Send documents for signature from your own code
The SignSecure API sends a document for signature, tells you the moment it is signed, and hands back the signed PDF with its audit trail. This quickstart sends a real one in five calls.
Quickstart
You will create an envelope with two signers, upload a PDF, place a signature field for each of them, send it, and download the signed result.
Every call is a plain HTTPS request with a JSON body, so any language works. The examples are in curl, Node.js 20 and Python with requests; pick one with the tabs on any example and the rest of the page follows.
There is no test mode yet. Sending an envelope emails real people and counts as a real send, so use your own addresses while you build.
1. Get an API key
An admin creates keys in the app, under Developers. Give the key a name you will recognise, such as the system that will use it, and copy it: it is shown once.
The API is part of the Business plan. If your account is on the free plan, an admin can upgrade on the Billing page.
Keep the key out of your code. The examples read it from an environment variable called SIGNSECURE_API_KEY.
Check it works by retrieving your account.
export SIGNSECURE_API_KEY="ss_live_..."
curl https://www.signsecure.org/v1/account \
-H "Authorization: Bearer $SIGNSECURE_API_KEY"// SIGNSECURE_API_KEY=ss_live_... node quickstart.mjs
const res = await fetch('https://www.signsecure.org/v1/account', {
headers: { Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}` },
});
console.log(await res.json());# SIGNSECURE_API_KEY=ss_live_... python quickstart.py
import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/account",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
print(res.json()){
"id": "org_1f3a5c7e9b0d2f4a6c8e1b",
"object": "account",
"name": "Your Company Ltd",
"plan": "business",
"…": "…"
}2. Create an envelope
An envelope holds everything about one request for signatures. Create it with a subject and the people who need to sign. Under the default sequential routing, Ben is asked once Amara has signed.
Put your own record id in metadata. It comes back in every webhook about this envelope, which is how you will know which record to update.
Keep the id of the envelope and of each recipient from the response.
curl https://www.signsecure.org/v1/v1/envelopes \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Order form, Northwind Traders",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
}
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subject: 'Order form, Northwind Traders',
metadata: {
crm_record: '006Qy00000AbCdE'
},
recipients: [
{
name: 'Amara Okafor',
email: 'amara.okafor@example.com'
},
{
name: 'Ben Hughes',
email: 'ben.hughes@example.com',
routing_order: 2
}
]
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"subject": "Order form, Northwind Traders",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
}
]
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "draft",
"subject": "Order form, Northwind Traders",
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"name": "Amara Okafor",
"…": "…"
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"name": "Ben Hughes",
"…": "…"
}
],
"…": "…"
}3. Upload the document
Upload the file people will sign. PDFs, Word .docx files and PNG or JPEG images all work, up to 8 MB; Word files and images are converted to PDF.
The response tells you the document's id and how many pages it has, which you need to place fields.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-F file=@order-form.pdfimport { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('order-form.pdf'), 'order-form.pdf');
const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
body: form,
});
const document = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
files={"file": open("order-form.pdf", "rb")},
)
document = res.json(){
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}4. Place the fields
Tell SignSecure where each person signs. Positions are fractions of the page from the top-left corner, so y: 0.78 is a little over three quarters of the way down, whatever size the page is. page_index counts from 0, so 2 is the third page.
A date field fills itself in with the day they sign. See the field object for the other types.
Not sure where the lines are on your document? Place the fields once in the app, save them as a template, and apply the template instead.
curl -X PUT https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06
}
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fields: [
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_7c2e9b1a4f6d8e0c3b5a7d',
type: 'signature',
page_index: 2,
x: 0.12,
y: 0.78,
width: 0.3,
height: 0.06
},
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_7c2e9b1a4f6d8e0c3b5a7d',
type: 'date',
page_index: 2,
x: 0.5,
y: 0.79,
width: 0.2,
height: 0.04
},
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_2d8f4b6a0c1e3f5a7b9c2e',
type: 'signature',
page_index: 2,
x: 0.12,
y: 0.88,
width: 0.3,
height: 0.06
}
]
}),
});
const envelope = await res.json();import os
import requests
res = requests.put(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"fields": [
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06
}
]
},
)
envelope = res.json()5. Send it
Sending emails Amara a link to sign. When she finishes, Ben gets his. The links work for 14 days here; the default is 30.
Once an envelope is sent, its documents, recipients and fields are fixed.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_in_days": 14
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
expires_in_days: 14
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"expires_in_days": 14
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"sent_at": 1791576300,
"expires_at": 1792785900,
"…": "…"
}6. Get the signed PDF
When the last person signs, the envelope's status becomes completed and the signed PDF is ready. There are two ways to find out.
Webhooks, which we recommend. Add an endpoint and SignSecure sends an envelope.completed event to your server the moment it happens, with your metadata in it. See the webhooks guide.
Polling. Retrieve the envelope now and then and check its status. Fine for a script; wasteful for anything bigger.
Then download the signed PDF. It has every signature and value written into it, and its SHA-256 is in signed_sha256 on the document.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-o order-form-signed.pdfimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
await writeFile('order-form-signed.pdf', Buffer.from(await res.arrayBuffer()));import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
open("order-form-signed.pdf", "wb").write(res.content)Next steps
That is the whole flow. From here:
- Receive webhooks and check their signatures.
- Make retries safe with idempotency keys, so a dropped connection never sends an envelope twice.
- Ask a signer for photo ID with
identity_requiredon a recipient. - Read the audit trail for evidence of who signed, when and from where.
- Questions, or something missing? Email support@signsecure.org.