Getting Started
Send your first envelope for signing using the SignSecure API.
This guide walks you through the full signing flow -- from creating an envelope to downloading the signed PDF -- with examples in multiple languages.
1. Create an API Key
Go to Settings > API Keys in the dashboard and create a new key. Copy it immediately -- it won't be shown again.
API keys inherit the workspace you're in when you create them. If you're in an organization workspace, the key operates under that organization's credits and envelopes. Switch to your personal workspace first if you want a personal API key.
Set your key as an environment variable:
export API_KEY="signsecure_your_key_here"$env:API_KEY = "signsecure_your_key_here"set API_KEY=signsecure_your_key_here2. Create an Envelope with Recipients
Create an envelope, add recipients, and get a presigned upload URL -- all in a single request. The recipients and workflow fields are optional here but save you extra API calls.
curl -X POST https://api.signpad.signsecure.in/api/v1/envelopes \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "signer",
"order": 1,
"signatureMethod": "electronic"
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"role": "approver",
"order": 2
}
],
"workflow": {
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all"
}
}'const API_KEY = process.env.API_KEY;
const BASE_URL = "https://api.signpad.signsecure.in/api/v1";
const response = await fetch(`${BASE_URL}/envelopes`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
fileName: "contract.pdf",
title: "Service Agreement",
recipients: [
{
name: "Jane Doe",
email: "jane@example.com",
role: "signer",
order: 1,
signatureMethod: "electronic",
},
{
name: "Bob Smith",
email: "bob@example.com",
role: "approver",
order: 2,
},
],
workflow: {
mode: "sequential",
verificationMethod: "email_verification",
message: "Please review and sign this agreement.",
emailNotifications: "all",
},
}),
});
const envelope = await response.json();
console.log(envelope.id); // "env_abc123"
console.log(envelope.upload.url); // S3 upload URL
console.log(envelope.recipientsAdded); // 2import os
import requests
API_KEY = os.environ["API_KEY"]
BASE_URL = "https://api.signpad.signsecure.in/api/v1"
response = requests.post(
f"{BASE_URL}/envelopes",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "signer",
"order": 1,
"signatureMethod": "electronic",
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"role": "approver",
"order": 2,
},
],
"workflow": {
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all",
},
},
)
envelope = response.json()
print(envelope["id"]) # "env_abc123"
print(envelope["upload"]["url"]) # S3 upload URL
print(envelope["recipientsAdded"]) # 2$apiKey = getenv('API_KEY');
$baseUrl = 'https://api.signpad.signsecure.in/api/v1';
$ch = curl_init("$baseUrl/envelopes");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'fileName' => 'contract.pdf',
'title' => 'Service Agreement',
'recipients' => [
[
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'role' => 'signer',
'order' => 1,
'signatureMethod' => 'electronic',
],
[
'name' => 'Bob Smith',
'email' => 'bob@example.com',
'role' => 'approver',
'order' => 2,
],
],
'workflow' => [
'mode' => 'sequential',
'verificationMethod' => 'email_verification',
'message' => 'Please review and sign this agreement.',
'emailNotifications' => 'all',
],
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$envelope = json_decode($response, true);
echo $envelope['id']; // "env_abc123"
echo $envelope['recipientsAdded']; // 2package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("API_KEY")
baseURL := "https://api.signpad.signsecure.in/api/v1"
body, _ := json.Marshal(map[string]interface{}{
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": []map[string]interface{}{
{
"name": "Jane Doe", "email": "jane@example.com",
"role": "signer", "order": 1, "signatureMethod": "electronic",
},
{
"name": "Bob Smith", "email": "bob@example.com",
"role": "approver", "order": 2,
},
},
"workflow": map[string]interface{}{
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all",
},
})
req, _ := http.NewRequest("POST", baseURL+"/envelopes", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var envelope map[string]interface{}
json.NewDecoder(resp.Body).Decode(&envelope)
fmt.Println(envelope["id"]) // "env_abc123"
fmt.Println(envelope["recipientsAdded"]) // 2
}Response:
{
"id": "env_abc123",
"title": "Service Agreement",
"fileName": "contract.pdf",
"status": "draft",
"fileKey": "uploads/env_abc123/contract.pdf",
"upload": {
"url": "https://s3.amazonaws.com/...",
"fields": { "key": "...", "policy": "...", "signature": "..." }
},
"expiresIn": 3600,
"maxFileSize": 10485760,
"recipientsAdded": 2,
"createdAt": "2026-03-11T10:00:00.000Z"
}Recipient Roles
| Role | Description |
|---|---|
signer | Must sign the envelope |
approver | Must approve (no signature required) |
cc | Receives a copy, no action needed |
Signature Methods
| Method | Description |
|---|---|
electronic | Draw or type a signature (free) |
aadhaar_otp | Aadhaar eSign with OTP verification (costs credits) |
dsc_usb | Digital Signature Certificate via USB token (free) |
all | Recipient chooses at signing time |
Workflow Options
| Option | Values | Description |
|---|---|---|
mode | sequential, parallel | Sequential = recipients sign in order. Parallel = all sign at once |
verificationMethod | email_verification, link_only | Email verification = recipient verifies via code. Link only = direct link |
emailNotifications | all, completion_only, none | Controls when recipients get notified |
redirectUrl | HTTPS URL | Optional. Redirects signers back to your app after signing. Query params envelopeId, signerEmail, role, status, and event are appended automatically |
3. Upload the PDF
Use the presigned upload URL from the previous response to upload your file. Only application/pdf files up to 10 MB are supported.
curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdfimport { readFileSync } from "node:fs";
const fileBuffer = readFileSync("contract.pdf");
await fetch(envelope.upload.url, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: fileBuffer,
});with open("contract.pdf", "rb") as f:
requests.put(
envelope["upload"]["url"],
headers={"Content-Type": "application/pdf"},
data=f.read(),
)$fileData = file_get_contents('contract.pdf');
$ch = curl_init($envelope['upload']['url']);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/pdf'],
CURLOPT_POSTFIELDS => $fileData,
]);
curl_exec($ch);
curl_close($ch);fileData, _ := os.ReadFile("contract.pdf")
req, _ := http.NewRequest("PUT", uploadUrl, bytes.NewReader(fileData))
req.Header.Set("Content-Type", "application/pdf")
http.DefaultClient.Do(req)4. Send for Signing
Since we already included the workflow config in the create call, we just send. Workflow settings (mode, verification, email notifications, redirect URL, etc.) are always configured during envelope creation.
curl -X POST https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/send \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{}'const sendResult = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/send`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
}
).then((r) => r.json());response = requests.post(
f"{BASE_URL}/envelopes/{envelope['id']}/send",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={},
)
result = response.json()$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);req, _ := http.NewRequest("POST",
baseURL+"/envelopes/"+envelopeID+"/send",
bytes.NewBufferString("{}"))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()The send endpoint accepts no request body. All workflow configuration (mode, verification method, email notifications, redirect URL, message, etc.) must be set during envelope creation via POST /envelopes. See the Complete Workflow guide for the step-by-step approach.
5. Check Progress
Poll the signing status to track who has signed.
curl https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/status \
-H "Authorization: Bearer $API_KEY"const status = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/status`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then((r) => r.json());
console.log(`${status.completedCount}/${status.totalRecipients} signed`);
console.log(`Status: ${status.status}`); // "pending" or "completed"response = requests.get(
f"{BASE_URL}/envelopes/{envelope['id']}/status",
headers={"Authorization": f"Bearer {API_KEY}"},
)
status = response.json()
print(f"{status['completedCount']}/{status['totalRecipients']} signed")
print(f"Status: {status['status']}") # "pending" or "completed"$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/status");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
]);
$status = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "{$status['completedCount']}/{$status['totalRecipients']} signed";req, _ := http.NewRequest("GET",
baseURL+"/envelopes/"+envelopeID+"/status", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var status map[string]interface{}
json.NewDecoder(resp.Body).Decode(&status)
fmt.Printf("%.0f/%.0f signed\n", status["completedCount"], status["totalRecipients"])Response:
{
"envelopeId": "env_abc123",
"status": "pending",
"totalRecipients": 2,
"completedCount": 1,
"percentage": 50,
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"status": "signed",
"signedAt": "2026-03-11T11:20:00.000Z",
"order": 1
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"status": "pending",
"order": 2
}
]
}Instead of polling, set up Webhooks to get notified in real-time when recipients sign.
6. Download the Signed PDF
Once the envelope status is completed, get a download URL for the signed PDF.
curl https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/file \
-H "Authorization: Bearer $API_KEY"const download = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/file`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then((r) => r.json());
console.log(download.url); // temporary S3 URL, valid for 1 hourresponse = requests.get(
f"{BASE_URL}/envelopes/{envelope['id']}/file",
headers={"Authorization": f"Bearer {API_KEY}"},
)
download = response.json()
print(download["url"]) # temporary S3 URL, valid for 1 hour$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/file");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
]);
$download = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $download['url']; // temporary S3 URL, valid for 1 hourreq, _ := http.NewRequest("GET",
baseURL+"/envelopes/"+envelopeID+"/file", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var download map[string]interface{}
json.NewDecoder(resp.Body).Decode(&download)
fmt.Println(download["url"]) // temporary S3 URL, valid for 1 hourResponse:
{
"url": "https://s3.amazonaws.com/...",
"expiresIn": 3600,
"fileName": "contract.pdf"
}Next Steps
- Set up Webhooks to get notified in real-time instead of polling
- Use Templates to reuse envelope layouts with predefined recipients
- Check your Credits balance and usage
- Read the Complete Workflow guide for every API action with examples