with one click
sendforsign
// Use for SendForSign document-signing workflows via API: create/send/sign contracts, manage templates/placeholders/recipients/webhooks/clients/users, upload/download documents, and handle Russian and English requests.
// Use for SendForSign document-signing workflows via API: create/send/sign contracts, manage templates/placeholders/recipients/webhooks/clients/users, upload/download documents, and handle Russian and English requests.
| name | sendforsign |
| description | Use for SendForSign document-signing workflows via API: create/send/sign contracts, manage templates/placeholders/recipients/webhooks/clients/users, upload/download documents, and handle Russian and English requests. |
| version | 1.0.0 |
This skill enables full interaction with the SendForSign API — a document signing platform. You can perform every operation available through their API.
Load these required resources before execution:
Before making any API call, read credentials from the .env file in the current working directory:
# Read API key
grep SENDFORSIGN_API_KEY .env | cut -d '=' -f2
# Read optional client key
grep SENDFORSIGN_CLIENT_KEY .env | cut -d '=' -f2
If .env doesn't exist or the key is missing, ask the user to provide it or add it to .env:
SENDFORSIGN_API_KEY=your_key_here
SENDFORSIGN_CLIENT_KEY=your_client_key_here # optional
Base URL: https://api.sendforsign.com/api
Standard request pattern (use curl via Bash):
curl -s -X POST https://api.sendforsign.com/api/<endpoint> \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {...}}'
Always show the user:
For full endpoint reference, see SendForSign API Reference.
Trigger: user asks to "markup", "annotate", or "add placeholders to" an existing contract/template — or says something like "разметь документ", "поставь плейсхолдеры".
Document markup means: find all dynamic (variable) values in the document — things that change between different instances of the contract (names, dates, cities, amounts, etc.) — and replace them with placeholders that cover exactly those text spans.
python3 ~/.claude/skills/sendforsign/scripts/render_pdf.py /path/to/doc.pdf /tmp/sfs_pages
Analyze text elements across all pages. Look for:
Group findings into named placeholders. If the same value appears in multiple places (e.g. both parties' signature pages), one placeholder covers all occurrences via multiple insertion entries.
For each placeholder:
insertion array — one entry per occurrence, each with exact api_x/api_y/api_w/api_h from the render script outputShow the user a summary table of all placeholders created, what text they cover, and how many pages.
Find dynamic values in the HTML value field and replace them inline with placeholder tags (see Case 3 for tag format). Update the contract's HTML via POST /api/contract (action: "update") — this embeds the tags into the document body.
Note: updating the contract HTML is NOT the same as updating a placeholder. To change a placeholder's name, value, or position — always use
POST /api/placeholder(action: "update"). Never use the contract or template update endpoint for that.
If the user provides a Word file, STOP and ask before doing anything:
"How would you like to upload this document?
- Editable document — text is extracted and uploaded as HTML. The document will be editable in SFS, but formatting may slightly change.
- PDF with fields — document is converted to PDF. Styling is preserved exactly; sign fields can be added after upload."
# Check pandoc
which pandoc
# If missing on macOS:
brew install pandoc
# Check LibreOffice (fallback)
which soffice || which libreoffice
# If missing on macOS:
brew install --cask libreoffice
# If missing on Ubuntu/Debian:
apt-get install -y libreoffice fonts-liberation fonts-dejavu
# If missing on CentOS/RHEL:
yum install -y libreoffice fonts-liberation
pandoc "file.docx" -o /tmp/sfs_doc.html --wrap=none 2>/dev/null || \
soffice --headless --convert-to html "file.docx" --outdir /tmp/
<html>, <head>, <body>, meta tags), keep only the <body> content.value.After the contract is created, fetch its value via action: "read", apply the following fixes in Python, then push back via action: "update".
Fix checklist:
<p> containing the document title (usually all-caps or in the first 1–2 paragraphs). Add centering and bold:<p style="text-align: center; margin-bottom: 2px;"><strong>ДОГОВОР ПОСТАВКИ</strong></p>
<h2>) — pandoc converts numbered clauses to <h2>, making them look like huge headings. Convert all <h2> to <p>:html = re.sub(r'<h2>', '<p style="text-align: justify; margin: 6px 0; line-height: 1.5;">', html)
html = re.sub(r'</h2>', '</p>', html)
<h3>) — convert to indented <p>:html = re.sub(r'<h3>', '<p style="text-align: justify; margin: 4px 0 4px 36px; line-height: 1.5;">', html)
html = re.sub(r'</h3>', '</p>', html)
<h1>) — keep as headings but add spacing:html = re.sub(r'<h1>', '<h1 style="font-size: 13pt; margin: 20px 0 8px; text-transform: uppercase;">', html)
style="width: 100%" on the table element. Instead:
data-full="true" to <table>, <colgroup>, and every <col>style attribute (keep only margin-right: auto;)<col> elements (equal split by default, e.g. 50%/50% for 2-col, 33%/33%/33% for 3-col)import re
# Remove fixed pixel width from table style
html = re.sub(
r'(style="margin-right: auto;) width: \d+px;"',
r'\1"',
html
)
# Add data-full="true" to <table> tags that have ql-table class
html = re.sub(
r'(<table class="ql-table"[^>]*?)(?! data-full)(>)',
r'\1 data-full="true"\2',
html
)
# Add data-full="true" to <colgroup> tags
html = re.sub(
r'(<colgroup[^>]*?)(?! data-full)(>)',
r'\1 data-full="true"\2',
html
)
# Convert fixed pixel col widths to equal percentages
# Count cols per colgroup, assign equal % widths, add data-full="true"
def fix_colgroup(m):
colgroup = m.group(0)
cols = re.findall(r'<col ', colgroup)
n = len(cols)
pct = f"{100 // n}%"
# Replace each <col ... > with percentage width + data-full
colgroup = re.sub(
r'<col([^>]*)>',
lambda c: f'<col{re.sub(r" width=\"[^\"]*\"", "", c.group(1))} width="{pct}" data-full="true">',
colgroup
)
return colgroup
html = re.sub(r'<colgroup[^>]*>.*?</colgroup>', fix_colgroup, html, flags=re.DOTALL)
<td> cells:html = re.sub(
r'<td class="ql-table-cell"',
'<td class="ql-table-cell" style="padding: 8px 10px; vertical-align: top;"',
html
)
# Check
which soffice || which libreoffice
# If missing on macOS:
brew install --cask libreoffice
# If missing on Ubuntu/Debian:
apt-get install -y libreoffice fonts-liberation fonts-dejavu
# If missing on CentOS/RHEL:
yum install -y libreoffice fonts-liberation
soffice --headless --convert-to pdf "file.docx" --outdir /tmp/
Before making any API calls, determine the correct flow based on what the user needs. There are five distinct cases. Pick exactly one.
Updating a placeholder (its name, value, or position on the page) is ALWAYS done via:
POST /api/placeholder action: "update"
Never use POST /api/contract action: "update" or POST /api/template action: "update" to change placeholder data — those endpoints only update the document's HTML body (to embed or re-embed placeholder tags), not the placeholder configuration itself.
| Goal | Correct endpoint |
|---|---|
| Change placeholder name, value, or insertion position | POST /api/placeholder → action: "update" |
| Embed placeholder tags into the document text | POST /api/contract → action: "update" |
| Embed placeholder tags into a template | POST /api/template → action: "update" |
Before creating new placeholders in a contract, always fetch the full placeholder list first, including auto-created special placeholders. To get them all, add the X-Sendforsign-Component header:
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "X-Sendforsign-Component: true" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "list",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY"
}
}'
Without X-Sendforsign-Component: true only regular placeholders are returned. With this header, you also get the special placeholders (signature, date signed, fullname, email) that the system auto-creates when a recipient is added.
Each placeholder in the response has an id field. This id is the N that goes into the HTML tag. Example response:
id: "1", name: "Name9", isSpecial: false → <placeholder1 ...>
id: "2", name: "Илья's signature", isSpecial: true, specialType: 4 → <sign2 ...>
id: "2", name: "Илья's date signed", isSpecial: true, specialType: 1 → <date2 ...>
id: "2", name: "Илья's full name", isSpecial: true, specialType: 2 → <fullname2 ...>
id: "2", name: "Илья's email", isSpecial: true, specialType: 3 → <email2 ...>
id: "3", name: "Иван's signature", isSpecial: true, specialType: 4 → <sign3 ...>
Key rules:
id field = the N in all HTML tags (<placeholderN>, <signN>, <dateN>, <fullnameN>, <emailN>)idisSpecial: false) are created manually via the APIid in the list and use the next numberexternalRecipientKey field on special placeholders links them to the recipientplaceholderKey for special placeholders follows the pattern {recipientKey}_{specialType} (e.g., e8c68...c4c_4 for signature)Condition: user wants a regular (HTML/text) contract without any fillable fields.
Steps:
curl -s -X POST https://api.sendforsign.com/api/contract \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contract": {
"name": "Contract Name",
"value": "<p>Contract text in HTML</p>"
}
}
}'
Condition: user has a PDF file and just wants to upload it as a contract, without adding any sign fields.
Steps — strictly in this order:
"contractType": "pdf" — this tells the API to expect a PDF upload.# Step 1: Create PDF-type contract
curl -s -X POST https://api.sendforsign.com/api/contract \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contract": {
"name": "Contract Name",
"contractType": "pdf"
}
}
}'
# → save contractKey from response
# Step 2: Upload the PDF
curl -s -X POST "https://api.sendforsign.com/api/upload_pdf?clientKey=CLIENT_KEY&contractKey=CONTRACT_KEY" \
-H "X-Sendforsign-Key: $API_KEY" \
-F "pdf=@/path/to/document.pdf;type=application/pdf"
Condition: user wants a text/HTML contract with fillable fields — either simple text fields (company name, date, address) or recipient-bound fields (signature, date signed, fullname, email).
In text contracts, placeholders are embedded directly in the HTML as special tags. There are NO separate API calls to position them (unlike PDF contracts). Instead, you insert special HTML tags into the contract's value field and call contract update.
There are two types of placeholders in text contracts:
A simple fillable text field. Use this for things like company name, address, amounts, dates, etc. These are created manually via POST /api/placeholder with action "create".
HTML tag pattern:
<p><span style="background-color: rgb(250, 250, 250);"><placeholderN class="placeholderClassN" contenteditable="false">{{{Placeholder Name}}}</placeholderN></span></p>
Rules:
N is the placeholder's id from the placeholder list response (NOT an arbitrary number — fetch the list first!)placeholderClassN where N matches the idcontenteditable="false" is mandatory{{{ }}} is the placeholder's visible labelrgb(250, 250, 250) (light gray) for regular placeholdersExamples:
<p><span style="background-color: rgb(250, 250, 250);"><placeholder1 class="placeholderClass1" contenteditable="false">{{{Company Name}}}</placeholder1></span></p>
<p><span style="background-color: rgb(250, 250, 250);"><placeholder2 class="placeholderClass2" contenteditable="false">{{{Contract Amount}}}</placeholder2></span></p>
Fields that auto-fill with recipient data or require action from the recipient. There are exactly 4 sub-types, each with its own tag pattern.
These are created AUTOMATICALLY by the system when you create a recipient — do NOT create them manually. After creating a recipient, fetch the placeholder list (with X-Sendforsign-Component: true) to discover the auto-created special placeholders and their id values.
The N in the HTML tags = the id field from the placeholder list response. All 4 special placeholders for the same recipient share the same id.
Background color for all recipient placeholders: rgb(245, 150, 0) (orange).
B1 — Recipient's signature:
<p><span style="background-color: rgb(245, 150, 0);"><signN class="signClassN" contenteditable="false">{{{RecipientName's signature}}}</signN></span></p>
B2 — Recipient's date signed:
<p><span style="background-color: rgb(245, 150, 0);"><dateN class="dateClassN" contenteditable="false">{{{RecipientName's date signed}}}</dateN></span></p>
B3 — Recipient's full name:
<p><span style="background-color: rgb(245, 150, 0);"><fullnameN class="fullnameClassN" contenteditable="false">RecipientName</fullnameN></span></p>
B4 — Recipient's email:
<p><span style="background-color: rgb(245, 150, 0);"><emailN class="emailClassN" contenteditable="false">recipient@email.com</emailN></span></p>
| Tag | class | Content | Meaning |
|---|---|---|---|
<signN> | signClassN | {{{Name's signature}}} | Signature field |
<dateN> | dateClassN | {{{Name's date signed}}} | Auto-filled date |
<fullnameN> | fullnameClassN | RecipientName (plain text, no {{{ }}}) | Auto-filled name |
<emailN> | emailClassN | recipient@email.com (plain text, no {{{ }}}) | Auto-filled email |
<placeholderN> | placeholderClassN | {{{Label}}} | Regular text field |
Note: fullname and email tags use plain text as content (no triple braces). sign, date, and regular placeholder tags use {{{ }}} wrapping.
Steps — strictly in this order:
X-Sendforsign-Component: true header) to get all placeholders and their id values.id from each placeholder as the N in tags.value field with the new HTML.# Step 1: Create the contract
# (same as Case 1, save contractKey)
# Step 2: Create recipients (only if using TYPE B recipient placeholders)
curl -s -X POST https://api.sendforsign.com/api/recipient \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"recipients": [
{
"action": "sign",
"fullname": "Ivan Petrov",
"email": "ivan@example.com",
"position": 1
}
]
}
}'
# → System auto-creates 4 special placeholders for Ivan
# Step 3: Create regular placeholders (only for TYPE A text fields)
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholder": {
"name": "Company Name",
"value": ""
}
}
}'
# Step 4: Fetch ALL placeholders to get their id values
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "X-Sendforsign-Component: true" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "list",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY"
}
}'
# → Response example:
# id: "1", name: "Company Name", isSpecial: false → use <placeholder1>
# id: "2", name: "Ivan Petrov's signature", isSpecial: true, type: 4 → use <sign2>
# id: "2", name: "Ivan Petrov's date signed", isSpecial: true, type: 1 → use <date2>
# id: "2", name: "Ivan Petrov's full name", isSpecial: true, type: 2 → use <fullname2>
# id: "2", name: "Ivan Petrov's email", isSpecial: true, type: 3 → use <email2>
# Steps 5-6: Update contract value with placeholders embedded in HTML
# Use the id values from step 4 as N in the tags!
curl -s -X POST https://api.sendforsign.com/api/contract \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "update",
"clientKey": "CLIENT_KEY",
"contract": {
"contractKey": "CONTRACT_KEY",
"value": "<p>Contract between parties.</p><p>Company: <span style=\"background-color: rgb(250, 250, 250);\"><placeholder1 class=\"placeholderClass1\" contenteditable=\"false\">{{{Company Name}}}</placeholder1></span></p><p>Signed by:</p><p><span style=\"background-color: rgb(245, 150, 0);\"><fullname2 class=\"fullnameClass2\" contenteditable=\"false\">Ivan Petrov</fullname2></span></p><p><span style=\"background-color: rgb(245, 150, 0);\"><sign2 class=\"signClass2\" contenteditable=\"false\">{{{Ivan Petrov'"'"'s signature}}}</sign2></span></p><p><span style=\"background-color: rgb(245, 150, 0);\"><date2 class=\"dateClass2\" contenteditable=\"false\">{{{Ivan Petrov'"'"'s date signed}}}</date2></span></p>"
}
}
}'
No separate "place" API call is needed for text contracts. The placeholders live inside the HTML.
Assume placeholder list returned: regular placeholder id=1, Ivan's specials id=2, Maria's specials id=3.
<!-- Regular placeholder uses its id from the list (id=1) -->
<p><span style="background-color: rgb(250, 250, 250);"><placeholder1 class="placeholderClass1" contenteditable="false">{{{Contract Amount}}}</placeholder1></span></p>
<!-- Ivan's fields use id=2 (from placeholder list) -->
<p><span style="background-color: rgb(245, 150, 0);"><sign2 class="signClass2" contenteditable="false">{{{Ivan's signature}}}</sign2></span></p>
<p><span style="background-color: rgb(245, 150, 0);"><date2 class="dateClass2" contenteditable="false">{{{Ivan's date signed}}}</date2></span></p>
<!-- Maria's fields use id=3 (from placeholder list) -->
<p><span style="background-color: rgb(245, 150, 0);"><sign3 class="signClass3" contenteditable="false">{{{Maria's signature}}}</sign3></span></p>
<p><span style="background-color: rgb(245, 150, 0);"><date3 class="dateClass3" contenteditable="false">{{{Maria's date signed}}}</date3></span></p>
The N in every tag MUST come from the id field in the placeholder list response. Never guess or hardcode it.
Condition: user has a PDF and wants to add text fields (not signature-related) at specific positions on the pages — e.g., a text box where a name or date will appear.
Steps — strictly in this order:
"contractType": "pdf".action: "create" to create it. Save the placeholderKey.action: "update" and an insertion array to position it on the page ("Place basic placeholder").# Steps 1-2: same as Case 2
# Step 4: Create the placeholder
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholder": {
"name": "company_name",
"value": ""
}
}
}'
# → save placeholderKey from response
# Step 5: Place the placeholder at a position on the PDF ("Place basic placeholder")
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "update",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholders": [
{
"placeholderKey": "PLACEHOLDER_KEY",
"insertion": [
{
"action": "update",
"clientKey": "CLIENT_KEY",
"id": "1",
"pageId": "0",
"positionX": "100",
"positionY": "200",
"width": "192",
"height": "25"
}
]
}
]
}
}'
Two separate API calls per placeholder: first "create", then "update" to place it. Never skip the create step.
Condition: user has a PDF and wants to add fields that are tied to a specific recipient — signature box, date of signing, recipient's name, or recipient's email.
Special placeholder types:
| specialType | Field | Who fills it |
|---|---|---|
| 1 | Date signed | Auto-filled by system when recipient signs |
| 2 | Recipient's full name | Auto-filled from recipient profile |
| 3 | Recipient's email | Auto-filled from recipient profile |
| 4 | Signature | Recipient draws/types their signature |
Steps — strictly in this order:
"contractType": "pdf".X-Sendforsign-Component: true) to discover the auto-created special placeholders and their placeholderKey values.action: "update" using isSpecial: true, specialType, and the placeholderKey from step 4 to position it on the page.# Steps 1-2: same as Case 2
# Step 3: Create recipient (repeat for each signer)
curl -s -X POST https://api.sendforsign.com/api/recipient \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"recipients": [
{
"action": "sign",
"fullname": "John Signer",
"email": "signer@example.com",
"position": 1
}
]
}
}'
# → System auto-creates 4 special placeholders for John
# Step 4: Fetch all placeholders to get their placeholderKey values
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "X-Sendforsign-Component: true" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "list",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY"
}
}'
# → Response includes auto-created special placeholders, e.g.:
# placeholderKey: "abc123_4", specialType: 4 (signature)
# placeholderKey: "abc123_1", specialType: 1 (date signed)
# placeholderKey: "abc123_2", specialType: 2 (fullname)
# placeholderKey: "abc123_3", specialType: 3 (email)
# Step 6: Place the special placeholder using placeholderKey from step 4
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "update",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholders": [
{
"placeholderKey": "abc123_4",
"isSpecial": true,
"specialType": 4,
"insertion": [
{
"action": "update",
"clientKey": "CLIENT_KEY",
"id": "1",
"pageId": "0",
"positionX": "144",
"positionY": "672",
"width": "192",
"height": "58"
}
]
}
]
}
}'
Two operations for special placeholders: create recipient (which auto-creates placeholders) → fetch placeholder list → place with "update". You do NOT manually create special placeholders — the system creates them for you.
Use this whenever the task involves placing any placeholder on a PDF page (Cases 4 and 5).
insertion field format rulesEvery insertion item MUST follow these rules, or the API will silently ignore the placement (returns 201 but nothing gets saved):
"id": "1", "pageId": "0", "positionX": "100" — NOT "id": 1, "pageId": 0, "positionX": 100id starts from "1", not "0". Use "1" for the first insertion, "2" for the second, etc.action": "update" and "clientKey" are required inside each insertion item — for BOTH basic and special placeholdersCorrect insertion example:
"insertion": [
{
"action": "update",
"clientKey": "CLIENT_KEY",
"id": "1",
"pageId": "0",
"positionX": "100",
"positionY": "200",
"width": "150",
"height": "50"
}
]
Common mistakes that cause silent failures:
"id": "0" or "id": 0 → field not placed"action": "update" → field not placed"clientKey" → field not placedRequires poppler (install with brew install poppler if missing):
python3 ~/.claude/skills/sendforsign/scripts/render_pdf.py /path/to/document.pdf /tmp/sfs_pages
The script does two things at once:
pdftohtml -xml with pre-calculated API coordinatesOutput per page:
image_path — PNG for visual inspectionpage_id — 0-indexed (use as pageId in API)text_elements — list of all text strings with exact API coords: api_x, api_y, api_w, api_hFor placeholders that cover existing text (the most common case):
Search text_elements for the target string and use its api_x/api_y/api_w/api_h directly:
# Example output for "19 марта 2025 года":
# {"text": "19 марта 2025 года", "api_x": 217, "api_y": 175, "api_w": 150, "api_h": 20}
Use api_x → positionX, api_y → positionY, api_w → width, api_h → height. No math needed.
For non-text areas (signature boxes, empty lines, images):
Read the image_path PNG visually. Since image is 1000px wide = API space, pixel coord = API coord directly.
For recipient sign fields placed in empty space below content:
positionY + 0: [Signature] w=192, h=58
positionY + 73: [Full name] w=192, h=25
positionY + 113: [Date signed] w=192, h=25
IMPORTANT — always check available vertical space before using standard dimensions.
Standard h=58 for signature is a default, not a guarantee. Before placing a signature field, check the gap between the target position and the next text element below:
# Find the next element below positionY on the same page
elements_below = [e for e in page['text_elements'] if e['api_y'] > positionY]
next_element_y = min(e['api_y'] for e in elements_below) if elements_below else page['height_px']
available_height = next_element_y - positionY
actual_height = min(58, available_height) # never exceed the available gap
If available_height < 58, use available_height as height (or leave a 2px margin). Never place a field that overlaps the next text element below it.
Find the last text element on the page: max(api_y + api_h for all text_elements). Add 100px margin for the first field.
For two recipients side by side: Recipient 1 positionX=70, Recipient 2 positionX=320.
For standard field dimensions (already expressed in 1000px API space — do NOT rescale these):
| Field type | API width | API height |
|---|---|---|
| Signature (specialType 4) | 192 | 58 |
| Full name (specialType 2) | 192 | 25 |
| Date signed (specialType 1) | 192 | 25 |
| Email (specialType 3) | 192 | 25 |
| Regular placeholder | varies | 25 |
(0,0) ─────────────────→ positionX (0 to 1000)
│
│ ┌──────────────┐ ← positionY
│ │ placeholder │
│ └──────────────┘
│ ↑ width, height
↓
positionY (0 to height×scale_to_api)
Origin is top-left of the page. Page width = 1000 API units always.
1000 / image_width_pxTemplates work like reusable contracts. The same Case flows apply — with two key rules:
templateKey instead of contractKey in all placeholder operationsSame as Case 1, but endpoint is /api/template:
curl -s -X POST https://api.sendforsign.com/api/template \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"template": {
"name": "Template Name",
"value": "<p>Template text in HTML</p>"
}
}
}'
Response: { templateKey, createTime }
Same sequence as Case 3 for contracts, but use templateKey everywhere contractKey appeared:
Steps — strictly in this order:
templateKeytemplateKey)templateKey)templateKey + X-Sendforsign-Component: true)id valuestemplateKey) — this embeds the placeholder tags into the template body. This is NOT how you update a placeholder's value or position; for that, always use POST /api/placeholder (action "update").Key difference from Case 3: every API call uses "templateKey": "..." instead of "contractKey": "...".
There is no templateType: "pdf" in the API. To create a PDF template:
Steps — strictly in this order:
curl -s -X POST https://api.sendforsign.com/api/template \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "convert",
"clientKey": "CLIENT_KEY",
"template": {
"name": "Template Name",
"contractKey": "CONTRACT_KEY"
}
}
}'
Note: content and placeholders are copied; placeholder values are cleared.
ALWAYS follow this sequence — never skip step 1:
Step 1 — Fetch template placeholders first
Before creating the contract, list all placeholders of the template and show them to the user:
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "list",
"clientKey": "CLIENT_KEY",
"templateKey": "TEMPLATE_KEY"
}
}'
Show the user a table of all placeholders (name, placeholderKey, current value). Ask for the values to fill in if not already provided.
Step 2 — Create contract and fill placeholders in one API call
Use the "Create from template + fill placeholders" call. Always include placeholderKey (from Step 1) for every placeholder — this guarantees the correct placeholder is targeted. Also include name for readability:
curl -s -X POST https://api.sendforsign.com/api/contract \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contract": {
"templateKey": "TEMPLATE_KEY",
"name": "New Contract from Template"
},
"placeholders": [
{ "placeholderKey": "abc-111", "name": "client_name", "value": "Acme Corp" },
{ "placeholderKey": "abc-123", "name": "date", "value": "2026-03-19" }
]
}
}'
Rule: always provide placeholderKey for each entry — it is the unambiguous identifier. name may be added alongside for readability but is not relied upon for matching.
Table placeholders let you inject a dynamic table (with headers and rows) into an HTML contract or template. This does NOT work for PDF contracts.
Step 1 — Create the placeholder (same as any regular placeholder):
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholder": {
"name": "goods_table",
"value": ""
}
}
}'
# → save placeholderKey from response
Step 2 — Embed the placeholder tag in the contract HTML (same as regular placeholder — see Case 3):
<p><span style="background-color: rgb(250, 250, 250);"><placeholderN class="placeholderClassN" contenteditable="false">{{{goods_table}}}</placeholderN></span></p>
Update the contract's HTML via POST /api/contract (action: "update") to embed the tag in the right place. This is a contract HTML update — not a placeholder update.
Step 3 — Fill it with table data via a separate update call using table instead of value:
curl -s -X POST https://api.sendforsign.com/api/placeholder \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "update",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY",
"placeholder": {
"placeholderKey": "PLACEHOLDER_KEY",
"table": {
"columns": ["Item", "Qty", "Price"],
"rows": [
["Widget A", 10, 9.99],
["Widget B", 5, 19.99]
]
}
}
}
}'
Note: placeholderKey and table are nested inside placeholder (singular), not at the top data level.
When using action: "create" with a templateKey, you can pass table instead of value directly in the placeholders array — no separate update call needed:
curl -s -X POST https://api.sendforsign.com/api/contract \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "create",
"clientKey": "CLIENT_KEY",
"contract": {
"templateKey": "TEMPLATE_KEY",
"name": "New Contract"
},
"placeholders": [
{ "placeholderKey": "abc-111", "name": "client_name", "value": "Acme Corp" },
{
"placeholderKey": "abc-222",
"name": "goods_table",
"table": {
"columns": ["Item", "Qty", "Price"],
"rows": [
["Widget A", 10, 9.99],
["Widget B", 5, 19.99]
]
}
}
]
}
}'
This is the key advantage of table placeholders in templates — pass table instead of value and the table is rendered automatically at contract creation time.
Rule: always include placeholderKey for every entry (both value and table placeholders) to guarantee correct targeting.
Every recipient (signer, approver, viewer) has a unique personal link for accessing the document:
https://app.sendforsign.com/sharing/{recipientKey}
To get all recipient links for a contract, list its recipients:
curl -s -X POST https://api.sendforsign.com/api/recipient \
-H "X-Sendforsign-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"action": "list",
"clientKey": "CLIENT_KEY",
"contractKey": "CONTRACT_KEY"
}
}'
The response includes recipientKey for each recipient. Build the link as:
https://app.sendforsign.com/sharing/{recipientKey}
Always provide these links after adding recipients to a contract — it's how each person accesses their copy to sign, approve, or view.
SENDFORSIGN_API_KEY in .env.SendForSign uses a 3-tier hierarchy:
clientKey)userKey)Most operations require a clientKey. If SENDFORSIGN_CLIENT_KEY is set in .env, use it as the default. Otherwise ask the user which client to operate on, or list clients first.
For natural language contract generation:
curl -s -X POST https://aiapi.sendforsign.com/webhook/aiapi \
-H "X-Sendforsign-Key: $API_KEY" \
-H "clientKey: $CLIENT_KEY" \
-H "secretKey: $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Create a freelance services agreement between Acme Corp and John Doe for web development"}'
Returns AI-generated contract text + a 30-minute preview URL.
For the complete API reference with all endpoints, parameters, and examples, read SendForSign API Reference.
Work with Bitrix24 (Битрикс24) via REST API and MCP docs: CRM, tasks, calendar, chat, channels, open lines, projects, timeman, drive, org structure, feed, and scenario workflows in Russian and English.
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
Research a topic from the last 30 days. Also triggered by 'last30'. Sources: Reddit, X, YouTube, Hacker News, Polymarket, web. Become an expert and write copy-paste-ready prompts.
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.
Best practices for Remotion - Video creation in React
Create and edit Excalidraw diagrams programmatically. Use when the user asks to create diagrams, flowcharts, architecture diagrams, wireframes, or any visual drawings in Excalidraw format (.excalidraw files).