FreeSign

Guide

Automate FreeSign from the command line

FreeSign's read and verify surface is plain HTTP — you can script it end to end with curl and standard shell tools, no SDK required. This guide shows the copy-paste examples for headless and CI use, and is honest about the one part that does not run from a shell: the signing ceremony itself.

What runs headless, and what does not

FreeSign endpoints split cleanly in two:

Headless reachability of the FreeSign surface
Surfacecurl / shell?Why
Local PDF hashingPure local shasum — nothing leaves the machine.
Hash lookup — GET /api/verifyUnauthenticated GET.
Receipts — GET /api/receipts/{id}Unauthenticated GET.
OpenTimestamps proof downloadUnauthenticated GET.
Document-free MCP tools — POST /mcpPlain JSON-RPC over HTTP.
PDF verification (openssl / pyHanko / ots)Offline tooling on the finished file.
The signing ceremony — /otp, /sign, /seal, /finalizeEach request must be signed by a non-extractable browser key — see the last section.

Everything in the curl column below works against production (https://free-sign.com) or a local wrangler dev instance (http://localhost:8787). And the rule never changes: the PDF stays on your machine. FreeSign endpoints accept hashes and evidence only.

1. Hash the PDF locally

Every FreeSign workflow starts with the document's SHA-256, computed where the file lives:

# macOS
DOC_HASH=$(shasum -a 256 contract.pdf | cut -d' ' -f1)

# Linux
DOC_HASH=$(sha256sum contract.pdf | cut -d' ' -f1)

echo "$DOC_HASH"

That value is document_sha256 — a lowercase 64-character hex string. It is the only thing about the document FreeSign ever sees.

2. Check whether a hash is already on file

Look up any envelopes and statuses bound to a document hash. Useful in CI to detect “has this artifact already been signed?”

curl -s "https://free-sign.com/api/verify?document_sha256=$DOC_HASH" | jq

Returns the matching envelopes with their statuses (draft, otp_sent, signed, finalized, expired). An empty result means the hash is not known to FreeSign.

3. Drive the document-free MCP endpoint with curl

The MCP server at /mcp is a JSON-RPC endpoint — no MCP client library needed. List the tools:

curl -s https://free-sign.com/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'

Call verify_document_hash — the same lookup as step 2, via MCP:

curl -s https://free-sign.com/mcp \
  -H 'content-type: application/json' \
  -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"verify_document_hash\",\"arguments\":{\"document_sha256\":\"$DOC_HASH\"}}}" | jq

The document-free tool set is create_signing_envelope, verify_document_hash, get_receipt, and get_ots_proof. None of them accepts PDF bytes — the MCP discovery contract advertises documentUpload: false.

4. Pull a signing receipt

Once an envelope is signed, its full evidence is retrievable by id:

EID=env_1f3a...c90b
curl -s "https://free-sign.com/api/receipts/$EID" | jq

The response carries {envelope, receipts, ots_anchors} — the seal metadata, the final-payload signature, and the OpenTimestamps anchors. It contains evidence only, never PDF content. For what each field means, see the evidence JSON schema reference.

5. Download and verify the OpenTimestamps proof

Every seal is anchored to the OpenTimestamps public calendar pool. Pull the .ots proof and verify it independently:

RECEIPT=$(curl -s "https://free-sign.com/api/receipts/$EID")
ANCHOR_HASH=$(echo "$RECEIPT" | jq -r '.ots_anchors[0].anchored_hash')
PROOF_URL=$(echo "$RECEIPT" | jq -r '.ots_anchors[0].proof_download')

curl -s -o proof.ots "https://free-sign.com$PROOF_URL"

# Verify against the anchored hash (the signed PDF's signed-region digest)
ots verify --digest "$ANCHOR_HASH" proof.ots

# If still calendar-pending, upgrade then re-verify — works any time later
ots upgrade proof.ots
ots verify --digest "$ANCHOR_HASH" proof.ots

6. Verify a finished PDF headlessly

To validate a signed PDF in one pass — structure, CMS signature, certificate chain, timestamp, OpenTimestamps proof, and the embedded evidence JSON — run the project validator:

node tools/validate-sealed-pdf.mjs signed.pdf

For a step-by-step verification with openssl and pyHanko instead, follow Verify a signed PDF with openssl, pyHanko, and OpenTimestamps. None of these checks contacts a FreeSign server — the trust anchors live in the file.

Running the signing ceremony without a human at the keyboard

The protected endpoints — /otp, /otp/verify, /sign, /seal, /finalize — cannot be driven by plain curl. Each one must carry an ECDSA P-256 signature produced by a key that is generated at envelope-creation time and held non-extractably in the browser. A shell has no such key, so a bare curl call to a protected endpoint returns 401.

Two honest paths exist:

One more thing, whichever path you take: automating the mechanics of a ceremony does not supply the authority to sign. An agent may operate a browser for a user, but it must not represent unattended automation as a human signature unless the human authorized that specific signing. See the llms.txt “Automation vs Signing Intent” section.

Building an integration?

The full REST surface is at /openapi.json, the agent skill at /free-sign-agent/SKILL.md. Or just sign one PDF by hand first.

Open free-sign.com →