Verification API
Verify signed documents programmatically. Look up by code, upload a PDF, query the transparency log, or verify entirely offline using embedded W3C credentials.
How Verification Works
Every document signed through SignForge embeds cryptographic proof directly inside the PDF. Verification uses a multi-layer cascade — each layer is self-sufficient, so verification works even if some layers are unavailable.
W3C VC
Validate the embedded Verifiable Credential with ECDSA P-256
DB Lookup
Match verification code against the signing database
Hash Lookup
Match document SHA-256 against the transparency log
ECDSA Fallback
Verify embedded JSON signature without any database
Endpoints
All verification endpoints are public and require no API key. Rate limited to prevent abuse.
Document Verification
/api/verify/{code}/api/verify/uploadTransparency Log
/api/transparency/latest/api/transparency/hash/{sha256}/api/transparency/entry/{id}/api/transparency/keysDID Discovery
/.well-known/did.json24h cacheVerify by Code
Every signed PDF includes a verification code (printed on the document and embedded in the QR code). The code format is sf_ followed by 12 alphanumeric characters.
curl https://signforge.io/api/verify/sf_abc123def456Response
{
"verified": true,
"verification_code": "sf_abc123def456",
"envelope_title": "Service Agreement",
"sender": {
"name": "Alex Chen",
"email": "alex@company.com"
},
"signer": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"signed_at": "2026-03-25T11:59:00Z",
"original_sha256": "a1b2c3...",
"signed_sha256": "d4e5f6...",
"page_count": 3,
"document_size_bytes": 245760,
"hash_match": true,
"method": "verifiable_credential",
"vc_valid": true,
"transparency_log": {
"entry_id": 142,
"merkle_root": "7f8e9d...",
"merkle_proof": ["a1b2c3...", "d4e5f6..."],
"signed_tree_head": "eyJhbGci...",
"logged_at": "2026-03-25T12:00:00Z"
}
}method indicates which verification layer confirmed the document: verifiable_credential, embedded_json, hash_lookup, or cryptographic_signature.
Verify by Upload
Upload a signed PDF and the API will extract the embedded proof, verify the cryptographic signatures, and check the transparency log. Supports files up to 15 MB.
Python
import requests
with open("signed_contract.pdf", "rb") as f:
resp = requests.post(
"https://signforge.io/api/verify/upload",
files={"file": ("signed_contract.pdf", f, "application/pdf")},
)
result = resp.json()
print("Verified:", result["verified"])
print("Method:", result["method"])
print("VC Valid:", result["vc_valid"])
if result.get("transparency_log"):
log = result["transparency_log"]
print(f"Log entry #{log['entry_id']}, root: {log['merkle_root'][:16]}...")Node.js
const fs = require("fs");
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("signed_contract.pdf")]), "signed_contract.pdf");
const resp = await fetch("https://signforge.io/api/verify/upload", {
method: "POST",
body: form,
});
const result = await resp.json();
console.log("Verified:", result.verified);
console.log("Method:", result.method);cURL
curl -X POST https://signforge.io/api/verify/upload \
-F "file=@signed_contract.pdf"Verify by Document Hash
If you have the SHA-256 hash of a signed PDF, you can look it up directly in the transparency log without uploading the file.
# Compute the SHA-256 hash of the signed PDF
HASH=$(sha256sum signed_contract.pdf | cut -d' ' -f1)
# Look up in the transparency log
curl https://signforge.io/api/transparency/hash/$HASHResponse
{
"entry_id": 142,
"document_hash": "d4e5f6a1b2c3...",
"envelope_id": "550e8400-e29b-41d4-a716-446655440000",
"verification_code": "sf_abc123def456",
"merkle_root": "7f8e9d...",
"merkle_proof": ["a1b2c3...", "d4e5f6..."],
"signed_tree_head": "eyJhbGci...",
"created_at": "2026-03-25T12:00:00Z"
}Offline Verification
The showpiece: no network required
Every signed PDF embeds a self-verifying signforge_proof.html containing the full proof bundle — W3C VC, JAdES JWS, public keys, Merkle proof, RFC 3161 timestamp, and signer identities. Open it in any browser to verify, or use our standalone packages below.
Python
pip install signforge-verify[pdf]"""Offline verification of a SignForge-signed PDF.
Requires: pip install signforge-verify[pdf]
PyPI: https://pypi.org/project/signforge-verify/
"""
from signforge_verify import verify
# Verify a signed PDF
result = verify("signed_contract.pdf")
print("Verified:", result["valid"])
# Check individual proofs
for name, check in result["checks"].items():
print(f" {name}: {check['status']} — {check.get('detail', '')}")
# Or verify a .proof.html file
result = verify("document.proof.html")
print("Verified:", result["valid"])# CLI usage
signforge-verify signed_contract.pdf
signforge-verify document.proof.html --jsonNode.js / TypeScript
npm install @signforge/verify// Offline verification of a SignForge-signed document
// npm: https://www.npmjs.com/package/@signforge/verify
// Zero runtime dependencies — uses Web Crypto API (Node 18+)
import { SignForgeVerifier } from '@signforge/verify';
import fs from 'fs';
const verifier = new SignForgeVerifier();
// Verify a .proof.html file
const html = fs.readFileSync('document.proof.html', 'utf-8');
const result = await verifier.verifyFromHtml(html);
console.log('Verified:', result.valid);
// Verify a signed PDF
const pdf = fs.readFileSync('signed_contract.pdf');
const pdfResult = await verifier.verifyFromPdf(pdf);
console.log('Verified:', pdfResult.valid);
// Check individual proofs
for (const [name, check] of Object.entries(result.checks)) {
console.log(` ${name}: ${check.status} — ${check.detail || ''}`);
}# CLI usage
npx @signforge/verify signed_contract.pdf
npx @signforge/verify document.proof.html --jsonTransparency Log
SignForge maintains a public Merkle transparency log — the same cryptographic structure used by Certificate Transparency to secure the web's TLS certificates. Every signed document is appended to the log, and the tree head is independently signed with a separate key.
Get current tree state
curl https://signforge.io/api/transparency/latest{
"tree_size": 142,
"merkle_root": "7f8e9da1b2c3d4e5f6...",
"signed_tree_head": "eyJhbGciOiJFUzI1NiIs...",
"updated_at": "2026-04-14T08:30:00Z"
}Get public keys
Two separate keys protect the system. The issuer key signs Verifiable Credentials. The log key signs tree heads. Compromise of one does not break the other.
curl https://signforge.io/api/transparency/keys{
"version": 1,
"issuer": {
"did_key": "did:key:zDnae...",
"fingerprint": "sha256:f83OJ3D2...",
"algorithm": "ECDSA P-256",
"purpose": "Signs W3C Verifiable Credential signing receipts",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2...",
"y": "x_FEzRu9..."
}
},
"log": {
"did_key": "did:key:zDnae...",
"fingerprint": "sha256:kH7bEMqz...",
"algorithm": "ECDSA P-256",
"purpose": "Signs transparency log Signed Tree Heads",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "kH7bEMqz...",
"y": "pR2sTuVw..."
}
}
}DID:web document
The keys are also published as a W3C DID document at the well-known endpoint, enabling automated discovery by verifiers and wallets.
curl https://signforge.io/.well-known/did.jsonResponse Schemas
Verify Response
| Field | Type | Description |
|---|---|---|
| verified | boolean | Whether the document is verified |
| verification_code | string | The sf_ code for this document |
| envelope_title | string | Title of the signed envelope |
| sender | object | { name, email } of the sender |
| signer | object | { name, email } of the signer |
| signed_at | ISO 8601 | When the document was signed |
| original_sha256 | string | Hash of the original PDF |
| signed_sha256 | string | Hash of the signed PDF |
| method | enum | verifiable_credential | embedded_json | hash_lookup | cryptographic_signature |
| vc_valid | boolean | Whether the W3C VC signature is valid |
| transparency_log | object | null | Merkle proof and tree head if available |
Transparency Log Entry
| Field | Type | Description |
|---|---|---|
| entry_id | integer | Sequential log entry ID |
| document_hash | string | SHA-256 hash of the signed PDF |
| envelope_id | UUID | ID of the envelope |
| verification_code | string | The sf_ verification code |
| merkle_root | string | Merkle tree root hash at time of logging |
| merkle_proof | string[] | Proof path for independent verification |
| signed_tree_head | string | ECDSA-signed tree head (JWS) |
| created_at | ISO 8601 | When the entry was logged |
Rate Limits
| Endpoint | Limit |
|---|---|
| GET /api/verify/{code} | 30 req/min |
| POST /api/verify/upload | 10 req/min |
| GET /api/transparency/* | 30 req/min |
| GET /.well-known/did.json | 30 req/min |
Rate limits are per IP address. Exceeding the limit returns HTTP 429. No authentication is needed for these endpoints.
AI-Assisted Verification
Users can verify documents using AI tools. Upload a signed PDF to ChatGPT or Claude, and the AI can hash it, query the transparency log API, and confirm authenticity.
Example prompt for ChatGPT or Claude
“I have a signed PDF from SignForge. Please compute the SHA-256 hash and check it against the SignForge transparency log at https://signforge.io/api/transparency/hash/[HASH]. The PDF contains an embedded signforge_proof.html with the full proof bundle (W3C VC, keys, Merkle proof). Extract it and verify the ECDSA signature.”
API Reference
Full REST API documentation with endpoint reference.
MCP Server
Use SignForge from Claude Desktop and AI agents.
Code Examples
Integration samples for n8n, Retool, cURL, and more.