Why This Integration Matters Right Now
The average contract takes 3–5 business days to close when it involves email threads, printing, scanning, and chasing signatures. With QuickSigner’s REST API, you can compress that to minutes. Add Google Gemini to the mix and you eliminate the manual drafting step entirely: the document writes itself, converts to PDF, uploads to the platform, and gets sent for signature — with zero human involvement in the loop.
This isn’t a thought experiment. It’s a working technical guide based on QuickSigner’s actual API documentation and Google Gemini’s current SDK, with production-ready Python code and concrete business use cases.
What Each Platform Brings to the Table
Google Gemini (API)
The current model family — Gemini 2.5 Flash (fast, cost-efficient), Gemini 2.5 Pro (complex reasoning), and the newer Gemini 3 Flash/Pro — offers capabilities directly relevant to document automation:
Guaranteed Structured Output (JSON Schema): Rather than parsing free-form text, you can constrain Gemini to return data that strictly matches a Pydantic schema. Extract full_name, job_title, start_date, and annual_salary from an onboarding email and you get a validated JSON object — not text that might be malformed on a bad day.
from pydantic import BaseModel, Field
class EmploymentDetails(BaseModel):
full_name: str
job_title: str
department: str
annual_salary_gbp: float = Field(description="Annual gross salary in GBP")
start_date: str = Field(description="ISO format: YYYY-MM-DD")
probation_months: int = Field(default=3)
email: str
mobile: str = Field(description="International format: +44XXXXXXXXXX or +1XXXXXXXXXX")
contract_type: str = Field(description="permanent / fixed-term / contractor")
Multimodal Understanding: Gemini can read scanned documents, passports, and ID cards and extract structured data from them — removing manual data entry from HR onboarding entirely.
1M Token Context Window: Large enough to include your full internal policy handbook as reference material, ensuring generated contracts stay consistent with your company’s standard terms.
QuickSigner (API)
QuickSigner exposes a clean REST API documented at quicksigner.stoplight.io, available on Pro and Enterprise plans. The core flow involves two steps:
1. Upload a document — POST /upload
Accepts .doc, .docx, and .pdf files. Returns a unique filename identifier used in the next step.
2. Create a signing request — POST /request
Sends the document to one or more signatories. Supports sequential or parallel signing, one-factor (email only) or two-factor authentication (email + OTP SMS), custom expiry dates, and personalised messages.
Why it’s legally binding in the UK and USA:
- United States: Compliant with the ESIGN Act (Electronic Signatures in Global and National Commerce Act, 2000) and UETA (Uniform Electronic Transactions Act), which give e-signatures the same legal standing as wet-ink signatures across 49 US states.
- United Kingdom: Compliant with the Electronic Communications Act 2000 and the retained eIDAS regulation, which continues to apply post-Brexit under UK law.
- Technical standard: QuickSigner uses PAdES (PDF Advanced Electronic Signatures), the ISO/TSA-backed standard that embeds cryptographically secured, tamper-evident signatures directly into the PDF — ensuring they remain verifiable for years.
- Security: ISO/IEC 27001:2022 certified. Documents are stored on Google Cloud with advanced encryption. Metadata including IP addresses and timestamps is captured for every signing event, providing a complete audit trail.
Architecture: How It All Fits Together
┌──────────────────────────────────────────────────────────────────┐
│ DATA SOURCE │
│ Onboarding form / Email / CRM / Scanned ID / ERP │
└───────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ GOOGLE GEMINI API │
│ • Structured data extraction (Pydantic + JSON Schema) │
│ • Field validation and normalisation │
│ • Contract text generation from template variables │
│ • Output: type-safe JSON, guaranteed schema-compliant │
└───────────────────────────┬──────────────────────────────────────┘
│ Validated JSON
▼
┌──────────────────────────────────────────────────────────────────┐
│ BACKEND / CLOUD FUNCTION │
│ • Populate DOCX template (python-docx / Jinja2) │
│ • Convert to PDF (LibreOffice headless / WeasyPrint) │
└───────────────────────────┬──────────────────────────────────────┘
│ PDF
▼
┌──────────────────────────────────────────────────────────────────┐
│ QuickSigner API │
│ Step 1: POST /upload → returns `filename` │
│ Step 2: POST /request → signing request + signatories │
│ Step 3: Webhook (opt.) → notify when signed │
└───────────────────────────┬──────────────────────────────────────┘
│
▼
📧 Signatory receives secure link
📱 Signs via OTP (2FA, if enabled)
✅ PAdES-signed PDF + full audit trail
Total time from raw data to document sent for signature: under 2 minutes.
Step-by-Step Implementation (Python)
Setup
pip install google-genai python-docx pydantic requests
# For DOCX → PDF conversion on Linux servers:
apt-get install libreoffice --headless
Step 1: Client Initialisation
import os
import requests
from google import genai
from pydantic import BaseModel, Field
from pathlib import Path
# Gemini — use the new google-genai SDK (not the legacy google-generativeai)
gemini_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
# QuickSigner
QS_BASE_URL = "https://app.quicksigner.com/api"
QS_HEADERS = {
"Authorization": f"Bearer {os.environ['QUICKSIGNER_TOKEN']}",
"Content-Type": "application/json",
}
Security note: Never hardcode API tokens. Use environment variables, AWS Secrets Manager, or a similar secrets store. Rotate tokens regularly and scope them to minimum required permissions.
Step 2: Extract Structured Data with Gemini
class EmploymentDetails(BaseModel):
full_name: str
job_title: str
department: str
annual_salary_gbp: float
start_date: str # ISO: YYYY-MM-DD
probation_months: int = 3
email: str
mobile: str
contract_type: str # permanent / fixed-term / contractor
notice_period_weeks: int = 4
def extract_from_email(email_text: str) -> EmploymentDetails:
"""
Uses Gemini Structured Output to extract employment details from
a hiring confirmation email. Returns a validated Pydantic object —
no fragile text parsing required.
"""
prompt = f"""
Extract the employment details from the email below.
Return data conforming exactly to the requested schema.
Infer missing fields from context where possible.
Dates must be in ISO format (YYYY-MM-DD).
Salary should be the annual gross figure in GBP.
Email:
---
{email_text}
---
"""
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config={
"response_mime_type": "application/json",
"response_json_schema": EmploymentDetails.model_json_schema(),
}
)
return EmploymentDetails.model_validate_json(response.text)
Step 3: Populate Template and Generate PDF
import subprocess
from docx import Document
def generate_contract_pdf(details: EmploymentDetails, template_path: str) -> Path:
"""
Populates a DOCX template with employment details and converts to PDF.
Templates use {{PLACEHOLDER}} syntax.
"""
doc = Document(template_path)
substitutions = {
"{{FULL_NAME}}": details.full_name,
"{{JOB_TITLE}}": details.job_title,
"{{DEPARTMENT}}": details.department,
"{{ANNUAL_SALARY}}": f"£{details.annual_salary_gbp:,.2f}",
"{{START_DATE}}": details.start_date,
"{{PROBATION_PERIOD}}": f"{details.probation_months} months",
"{{NOTICE_PERIOD}}": f"{details.notice_period_weeks} weeks",
"{{CONTRACT_TYPE}}": details.contract_type.replace("-", " ").title(),
}
for paragraph in doc.paragraphs:
for key, value in substitutions.items():
if key in paragraph.text:
for run in paragraph.runs:
run.text = run.text.replace(key, value)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for para in cell.paragraphs:
for key, value in substitutions.items():
if key in para.text:
for run in para.runs:
run.text = run.text.replace(key, value)
docx_path = Path(f"/tmp/contract_{details.full_name.replace(' ', '_')}.docx")
pdf_path = docx_path.with_suffix(".pdf")
doc.save(docx_path)
subprocess.run(
["libreoffice", "--headless", "--convert-to", "pdf", "--outdir", "/tmp", str(docx_path)],
check=True, capture_output=True
)
return pdf_path
Step 4: Upload and Send for Signature via QuickSigner API
def upload_and_request_signature(
pdf_path: Path,
details: EmploymentDetails,
auth_type: str = "two_factor" # "one_factor" or "two_factor" (OTP SMS)
) -> dict:
"""
Uploads the PDF to QuickSigner and creates a signing request.
Returns the full API response including the request ID.
"""
# Step 4a: Upload document
with open(pdf_path, "rb") as f:
upload_resp = requests.post(
f"{QS_BASE_URL}/upload",
headers={"Authorization": QS_HEADERS["Authorization"]},
files={"file": (pdf_path.name, f, "application/pdf")}
)
upload_resp.raise_for_status()
filename = upload_resp.json()["filename"]
# Step 4b: Create signing request
payload = {
"filename": filename,
"title": f"Employment Agreement — {details.full_name}",
"message": (
f"Dear {details.full_name.split()[0]},\n\n"
"Please find your employment agreement attached for review and signature. "
"You can sign securely from any device — no downloads required.\n\n"
"If you have any questions before signing, please don't hesitate to reach out."
),
"signers": [
{
"name": details.full_name,
"email": details.email,
"phone": details.mobile, # Required for two_factor
"auth_type": auth_type,
"order": 1
}
],
"send_email": True,
"expires_at": "2025-12-31T23:59:59Z",
}
request_resp = requests.post(
f"{QS_BASE_URL}/request",
headers=QS_HEADERS,
json=payload
)
request_resp.raise_for_status()
return request_resp.json()
# --- FULL WORKFLOW ---
def process_new_hire(email_text: str, template_path: str) -> str:
"""
End-to-end: hiring email → signed employment agreement request.
Returns the QuickSigner request ID for tracking.
"""
try:
details = extract_from_email(email_text)
pdf_path = generate_contract_pdf(details, template_path)
result = upload_and_request_signature(pdf_path, details)
# Clean up temp files
pdf_path.unlink(missing_ok=True)
pdf_path.with_suffix(".docx").unlink(missing_ok=True)
request_id = result.get("id", "N/A")
print(f"✅ Contract sent for signature. Request ID: {request_id}")
return request_id
except requests.HTTPError as e:
print(f"❌ QuickSigner API error: {e.response.status_code} — {e.response.text}")
raise
except Exception as e:
print(f"❌ Workflow error: {e}")
raise
Common Pitfalls to Avoid
1. Using the legacy Gemini SDK
Many tutorials still use google-generativeai with genai.GenerativeModel(). The current SDK is google-genai with genai.Client(). The difference isn't just syntax — the new SDK natively supports Pydantic schemas and response_json_schema, removing the need for manual JSON parsing and the bugs that come with it.
# ❌ Legacy (google-generativeai)
import google.generativeai as genai
model = genai.GenerativeModel('gemini-1.5-flash')
# ✅ Current (google-genai)
from google import genai
client = genai.Client(api_key="...")
response = client.models.generate_content(model="gemini-2.5-flash", ...)
2. Prompting for JSON without a schema
Asking Gemini to "return a JSON object with these fields" in natural language gives you probabilistic output — it might include extra commentary, miss optional fields, or use inconsistent key names. With response_json_schema, output is schema-guaranteed or raises an exception. There's no middle ground of silent partial data.
3. Font inconsistencies in PDF conversion
LibreOffice headless is the most reliable server-side DOCX→PDF converter. If your template uses non-standard fonts (anything beyond Arial, Times New Roman, Calibri), install them on the server explicitly. Missing fonts cause silent fallback substitutions that make your output look unprofessional.
4. Sequential vs. parallel signing
QuickSigner supports both. If your workflow requires the line manager to sign before the employee (common in UK employment law for certain contract types), set "order": 1 for the manager and "order": 2 for the employee. Parallel signing (both simultaneously) is fine for vendor agreements where neither party needs to see the other's signature first.
5. GDPR and data residency (UK/US)
Employee data passing through the Gemini API is processed by Google. For UK operations, ensure your DPIA (Data Protection Impact Assessment) covers AI-assisted processing. For US operations, check state-level privacy laws — California (CCPA), Virginia (VCDPA), and others have specific requirements around automated processing of personal data. Use Gemini Enterprise if your organisation requires DPA coverage.Top 3 Use Cases with Real Business Impact
1. Automated HR Onboarding
The problem: HR teams spend an average of 45 minutes per new hire manually drafting and sending employment agreements, offer letters, and policy acknowledgement forms. At 30 hires a month, that’s 22+ hours of repetitive work — work that also carries a non-trivial risk of copy-paste errors (wrong salary, wrong start date, wrong name).
The solution: The recruiter sends a standard confirmation email. Gemini extracts all fields using structured output. The employment agreement, offer letter, and GDPR consent form are generated from templates and sent as a single signing package via QuickSigner.
Quantified impact: 45 minutes → under 3 minutes per hire. Human error on key contract fields: eliminated.
Bonus capability: Gemini’s multimodal API can read a photo of a passport or driving licence and extract name, date of birth, and address — removing even the data entry step from the HR workflow.
2. Vendor and Supplier Agreements
The problem: Procurement teams receive supplier quotes in different formats (PDF, email, Word). Comparing, approving, and issuing a formal purchase order takes 2–3 business days and involves multiple stakeholders.
The solution:
class SupplierQuote(BaseModel):
supplier_name: str
unit_price_usd: float
quantity: int
total_value_usd: float
delivery_days: int
quote_validity_days: int
payment_terms: str # e.g. "Net 30"
def evaluate_and_auto_approve(quote_pdf: bytes, budget_usd: float) -> str:
"""
Gemini reads the quote PDF and extracts structured data.
If within budget, issues a purchase order automatically via QuickSigner.
"""
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=[
{"inline_data": {"mime_type": "application/pdf", "data": quote_pdf}},
{"text": "Extract the quote details per the requested schema."}
],
config={
"response_mime_type": "application/json",
"response_json_schema": SupplierQuote.model_json_schema(),
}
)
quote = SupplierQuote.model_validate_json(response.text)
if quote.total_value_usd <= budget_usd:
return generate_and_send_po(quote)
else:
return f"Over budget (${quote.total_value_usd:,.2f} > ${budget_usd:,.2f}). Escalating to Finance."
Impact: Sub-budget orders are processed without human involvement. The procurement team’s attention is reserved for exceptions and negotiations — not paperwork.
3. High-Volume Contract Renewals and Amendments
The problem: A regulatory change, a price adjustment, or an updated SLA means sending amendments to hundreds or thousands of existing clients or employees. Manually generating and tracking each one is a project in itself.
The solution:
def send_bulk_amendments(
client_list: list[dict],
amendment_description: str,
effective_date: str
) -> list[str]:
"""
Generates personalised amendments for each client and sends via QuickSigner.
Returns a list of request IDs for tracking.
"""
request_ids = []
for client in client_list:
# Gemini personalises the amendment text per client context
prompt = f"""
Draft a formal contract amendment for:
Client: {client['company_name']}
Contact: {client['contact_name']}, {client['title']}
Existing contract: {client['contract_ref']}
Amendment: {amendment_description}
Effective date: {effective_date}
Tone: professional, concise. UK English.
Reference the existing contract by number.
Do not include a signature block — that will be added by the platform.
"""
amendment_text = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt
).text
pdf = generate_pdf_from_text(amendment_text, client)
result = upload_and_send(pdf, client)
request_ids.append(result["id"])
print(f"✅ {len(request_ids)} amendments dispatched for signature.")
return request_ids
Impact: 500 amendments drafted, uploaded, and sent in a few hours instead of weeks. QuickSigner tracks every signing event — you know in real time who has signed and who hasn’t, with automatic reminders built in.
Monitoring and Webhooks
Real-Time Status via Webhooks
Configure QuickSigner to POST to your endpoint when signing events occur:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhooks/quicksigner", methods=["POST"])
def handle_quicksigner_event():
event = request.json
match event.get("event"):
case "document.completed":
# All parties have signed — archive, update CRM, notify team
on_document_completed(event["request_id"])
case "signer.viewed":
# Signatory opened the document
log_event(event)
case "request.expired":
# Deadline passed without signature — escalate
on_request_expired(event)
return jsonify({"status": "received"}), 200
Key Metrics to Track in Production
- Gemini extraction success rate: Pydantic validation passing without exceptions — target >98%
- End-to-end processing time: From trigger to signing request sent — target under 10 seconds
- Signing completion rate within 24h: Strong indicator of signatory UX and email deliverability
- QuickSigner API error rate: Log all non-2xx responses with request payloads for debugging
QuickSigner vs DocuSign: The Practical Reality
For teams considering this integration who are currently on DocuSign or Adobe Sign:
| Factor | QuickSigner | DocuSign (Standard) |
|---|---|---|
| Price per document | ~$0.20 | $1.00–$2.00+ |
| API access | Pro plan required | Developer plan required |
| PAdES compliance | ✅ | ✅ |
| ISO 27001 certified | ✅ | ✅ |
| 2FA (OTP SMS) | ✅ | ✅ (add-on) |
| Setup complexity | Low | Medium–High |
At $0.20 per document, QuickSigner is cost-efficient enough to automate documents that you’d currently skip digitising (low-value NDAs, policy acknowledgements, internal approval forms) because the per-envelope cost on enterprise platforms makes them uneconomical.
What Not to Automate
Full automation is powerful, but some document types warrant a human checkpoint:
- Negotiated agreements: If terms vary per client, a human should review before dispatch
- High-value contracts: M&A documents, IP assignments, financing agreements — draft automatically, sign off manually
- Ambiguous data: If Gemini can’t confidently extract a required field, raise an alert rather than sending with a default value
- US state-specific requirements: Some states (e.g. California for employment contracts) have specific disclosure requirements — ensure your templates are reviewed by local counsel
A practical pattern: automate the standard flow, flag exceptions to a human queue with the extracted data pre-populated for one-click review.
Conclusion
The combination of Google Gemini’s structured output and QuickSigner’s API turns what was a 3-day manual process into a sub-2-minute automated workflow. The technical surface area is surprisingly small: two API calls to QuickSigner, one to Gemini, and a PDF conversion step in between.
The business case is equally straightforward. At $0.20 per document and Gemini API costs measurable in fractions of a cent per contract, the ROI calculation on even modest document volumes is immediate.
Get started: API documentation is at quicksigner.stoplight.io. Your API token is available from your QuickSigner account under Settings → API. For Gemini, grab a free API key from Google AI Studio.





