Complete Workflow
End-to-end API integration covering every action from setup to signed document retrieval.
This guide covers the complete SignSecure API workflow with production-ready examples. Each section is self-contained -- jump to the action you need.
Overview
The typical signing flow follows these steps:
Create API Key → Create Envelope → Upload PDF → Add Recipients
→ (Optional) Add Form Fields → Send for Signing
→ Track Progress → Download Signed PDFFor all examples below, set these variables first:
const API_KEY = process.env.API_KEY;
const BASE = "https://api.signpad.signsecure.in/api/v1";
async function api(path, options = {}) {
const response = await fetch(`${BASE}${path}`, {
...options,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(`${error.code}: ${error.message}`);
}
return response.json();
}import os
import requests
API_KEY = os.environ["API_KEY"]
BASE = "https://api.signpad.signsecure.in/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def api(method, path, **kwargs):
response = requests.request(method, f"{BASE}{path}", headers=HEADERS, **kwargs)
response.raise_for_status()
return response.json()$apiKey = getenv('API_KEY');
$base = 'https://api.signpad.signsecure.in/api/v1';
function api(string $method, string $path, ?array $body = null): array {
global $apiKey, $base;
$ch = curl_init("$base$path");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
$error = json_decode($response, true);
throw new Exception("{$error['code']}: {$error['message']}");
}
return json_decode($response, true);
}package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
var (
apiKey = os.Getenv("API_KEY")
base = "https://api.signpad.signsecure.in/api/v1"
)
func api(method, path string, body interface{}) (map[string]interface{}, error) {
var reqBody io.Reader
if body != nil {
b, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(b)
}
req, _ := http.NewRequest(method, base+path, reqBody)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("%s: %s", result["code"], result["message"])
}
return result, nil
}Envelopes
Create an Envelope
Creates a new envelope in draft status and returns a presigned upload URL.
const envelope = await api("/envelopes", {
method: "POST",
body: JSON.stringify({
fileName: "contract.pdf",
fileType: "application/pdf",
fileSize: 52400, // bytes
title: "Service Agreement",
}),
});
// envelope.id → "env_abc123"
// envelope.uploadUrl → presigned S3 URL
// envelope.status → "draft"envelope = api("POST", "/envelopes", json={
"fileName": "contract.pdf",
"fileType": "application/pdf",
"fileSize": 52400,
"title": "Service Agreement",
})
# envelope["id"] → "env_abc123"
# envelope["uploadUrl"] → presigned S3 URL
# envelope["status"] → "draft"$envelope = api('POST', '/envelopes', [
'fileName' => 'contract.pdf',
'fileType' => 'application/pdf',
'fileSize' => 52400,
'title' => 'Service Agreement',
]);
// $envelope['id'] → "env_abc123"
// $envelope['uploadUrl'] → presigned S3 URLenvelope, _ := api("POST", "/envelopes", map[string]interface{}{
"fileName": "contract.pdf",
"fileType": "application/pdf",
"fileSize": 52400,
"title": "Service Agreement",
})
envelopeID := envelope["id"].(string)
uploadURL := envelope["uploadUrl"].(string)Upload the PDF
Upload the PDF to the presigned URL. Only application/pdf files up to 10 MB are accepted.
import { readFileSync } from "node:fs";
await fetch(envelope.uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: readFileSync("contract.pdf"),
});with open("contract.pdf", "rb") as f:
requests.put(
envelope["uploadUrl"],
headers={"Content-Type": "application/pdf"},
data=f.read(),
)$ch = curl_init($envelope['uploadUrl']);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/pdf'],
CURLOPT_POSTFIELDS => file_get_contents('contract.pdf'),
]);
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)List Envelopes
Retrieve all envelopes for the current workspace.
const envelopes = await api("/envelopes");
// envelopes → array of envelope objectsenvelopes = api("GET", "/envelopes")$envelopes = api('GET', '/envelopes');envelopes, _ := api("GET", "/envelopes", nil)Get an Envelope
Retrieve a single envelope by ID.
const detail = await api(`/envelopes/${envelopeId}`);detail = api("GET", f"/envelopes/{envelope_id}")$detail = api('GET', "/envelopes/$envelopeId");detail, _ := api("GET", "/envelopes/"+envelopeID, nil)Update an Envelope
Update the title of a draft envelope.
const updated = await api(`/envelopes/${envelopeId}`, {
method: "PATCH",
body: JSON.stringify({ title: "Updated Agreement" }),
});updated = api("PATCH", f"/envelopes/{envelope_id}", json={
"title": "Updated Agreement",
})$updated = api('PATCH', "/envelopes/$envelopeId", [
'title' => 'Updated Agreement',
]);updated, _ := api("PATCH", "/envelopes/"+envelopeID, map[string]interface{}{
"title": "Updated Agreement",
})Delete an Envelope
Delete a draft envelope. Only envelopes in draft status can be deleted.
await api(`/envelopes/${envelopeId}`, { method: "DELETE" });api("DELETE", f"/envelopes/{envelope_id}")api('DELETE', "/envelopes/$envelopeId");api("DELETE", "/envelopes/"+envelopeID, nil)Get Download URL
Get a temporary download URL for the envelope's PDF (original or signed).
const download = await api(`/envelopes/${envelopeId}/file`);
// download.url → temporary S3 URL (valid 1 hour)
// download.fileName → "contract.pdf"download = api("GET", f"/envelopes/{envelope_id}/file")
# download["url"] → temporary S3 URL (valid 1 hour)$download = api('GET', "/envelopes/$envelopeId/file");
// $download['url'] → temporary S3 URL (valid 1 hour)download, _ := api("GET", "/envelopes/"+envelopeID+"/file", nil)
// download["url"] → temporary S3 URL (valid 1 hour)Recipients
Add Recipients
Add one or more recipients to a draft envelope.
const result = await api(`/envelopes/${envelopeId}/recipients`, {
method: "POST",
body: JSON.stringify({
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,
},
{
name: "Alice CC",
email: "alice@example.com",
role: "cc",
},
],
}),
});result = api("POST", f"/envelopes/{envelope_id}/recipients", json={
"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,
},
{
"name": "Alice CC",
"email": "alice@example.com",
"role": "cc",
},
]
})$result = api('POST', "/envelopes/$envelopeId/recipients", [
'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,
],
[
'name' => 'Alice CC',
'email' => 'alice@example.com',
'role' => 'cc',
],
],
]);result, _ := api("POST", "/envelopes/"+envelopeID+"/recipients", map[string]interface{}{
"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},
{"name": "Alice CC", "email": "alice@example.com", "role": "cc"},
},
})Roles and Signature Methods
| Role | Description |
|---|---|
signer | Must sign the document |
approver | Must approve (no signature) |
cc | Receives a copy only |
| Method | Cost | Description | Text Placement |
|---|---|---|---|
electronic | Free | Draw or type a signature | Supported |
aadhaar_otp | Credits | Aadhaar eSign with OTP | Supported (auto-resolved to coordinates) |
dsc_usb | Free | USB digital certificate | Not supported (coordinates only) |
all | Varies | Recipient chooses at signing | Supported (coordinates recommended) |
List Recipients
const recipients = await api(`/envelopes/${envelopeId}/recipients`);recipients = api("GET", f"/envelopes/{envelope_id}/recipients")$recipients = api('GET', "/envelopes/$envelopeId/recipients");recipients, _ := api("GET", "/envelopes/"+envelopeID+"/recipients", nil)Remove a Recipient
await api(`/envelopes/${envelopeId}/recipients/${recipientId}`, {
method: "DELETE",
});api("DELETE", f"/envelopes/{envelope_id}/recipients/{recipient_id}")api('DELETE', "/envelopes/$envelopeId/recipients/$recipientId");api("DELETE", "/envelopes/"+envelopeID+"/recipients/"+recipientID, nil)Form Fields
Place interactive fields on the PDF for recipients to fill out during signing.
Save Form Fields
const fields = await api(`/envelopes/${envelopeId}/fields`, {
method: "POST",
body: JSON.stringify({
fields: [
{
recipientId: "rec_abc123",
type: "signature",
page: 1,
x: 100,
y: 600,
width: 200,
height: 50,
required: true,
},
{
recipientId: "rec_abc123",
type: "date",
page: 1,
x: 100,
y: 660,
width: 150,
height: 30,
required: true,
},
{
recipientId: "rec_def456",
type: "signature",
page: 2,
x: 100,
y: 600,
width: 200,
height: 50,
required: true,
},
],
}),
});fields = api("POST", f"/envelopes/{envelope_id}/fields", json={
"fields": [
{
"recipientId": "rec_abc123",
"type": "signature",
"page": 1,
"x": 100, "y": 600,
"width": 200, "height": 50,
"required": True,
},
{
"recipientId": "rec_abc123",
"type": "date",
"page": 1,
"x": 100, "y": 660,
"width": 150, "height": 30,
"required": True,
},
{
"recipientId": "rec_def456",
"type": "signature",
"page": 2,
"x": 100, "y": 600,
"width": 200, "height": 50,
"required": True,
},
]
})$fields = api('POST', "/envelopes/$envelopeId/fields", [
'fields' => [
[
'recipientId' => 'rec_abc123',
'type' => 'signature',
'page' => 1,
'x' => 100, 'y' => 600,
'width' => 200, 'height' => 50,
'required' => true,
],
[
'recipientId' => 'rec_abc123',
'type' => 'date',
'page' => 1,
'x' => 100, 'y' => 660,
'width' => 150, 'height' => 30,
'required' => true,
],
[
'recipientId' => 'rec_def456',
'type' => 'signature',
'page' => 2,
'x' => 100, 'y' => 600,
'width' => 200, 'height' => 50,
'required' => true,
],
],
]);fields, _ := api("POST", "/envelopes/"+envelopeID+"/fields", map[string]interface{}{
"fields": []map[string]interface{}{
{"recipientId": "rec_abc123", "type": "signature", "page": 1, "x": 100, "y": 600, "width": 200, "height": 50, "required": true},
{"recipientId": "rec_abc123", "type": "date", "page": 1, "x": 100, "y": 660, "width": 150, "height": 30, "required": true},
{"recipientId": "rec_def456", "type": "signature", "page": 2, "x": 100, "y": 600, "width": 200, "height": 50, "required": true},
},
})Field Types
| Type | Description |
|---|---|
signature | Signature capture area |
text | Single-line text input |
textarea | Multi-line text input |
email | Email address field |
number | Numeric input |
date | Date picker |
checkbox | Checkbox |
radio | Radio button group |
dropdown | Dropdown selection |
List Form Fields
const fields = await api(`/envelopes/${envelopeId}/fields`);fields = api("GET", f"/envelopes/{envelope_id}/fields")$fields = api('GET', "/envelopes/$envelopeId/fields");fields, _ := api("GET", "/envelopes/"+envelopeID+"/fields", nil)Get Filled Values
Retrieve the values recipients have filled in after signing.
const values = await api(`/envelopes/${envelopeId}/fields/values`);
// values → array of { fieldId, recipientId, value, filledAt }values = api("GET", f"/envelopes/{envelope_id}/fields/values")$values = api('GET', "/envelopes/$envelopeId/fields/values");values, _ := api("GET", "/envelopes/"+envelopeID+"/fields/values", nil)Signing Workflow
Send Envelope
Send the envelope to all recipients to begin the signing process. No request body is needed -- workflow settings are configured during envelope creation via POST /envelopes.
const result = await api(`/envelopes/${envelopeId}/send`, {
method: "POST",
});result = api("POST", f"/envelopes/{envelope_id}/send")$result = api('POST', "/envelopes/$envelopeId/send");result, _ := api("POST", "/envelopes/"+envelopeID+"/send", nil)Check Signing Progress
const status = await api(`/envelopes/${envelopeId}/status`);
console.log(status.status); // "pending" | "completed"
console.log(status.percentage); // 50
console.log(status.completedCount); // 1
console.log(status.totalRecipients); // 2
for (const r of status.recipients) {
console.log(`${r.name}: ${r.status}`); // "signed" | "pending"
}status = api("GET", f"/envelopes/{envelope_id}/status")
print(status["status"]) # "pending" | "completed"
print(status["percentage"]) # 50
print(status["completedCount"]) # 1
for r in status["recipients"]:
print(f"{r['name']}: {r['status']}")$status = api('GET', "/envelopes/$envelopeId/status");
echo $status['status']; // "pending" | "completed"
echo $status['percentage']; // 50
foreach ($status['recipients'] as $r) {
echo "{$r['name']}: {$r['status']}\n";
}status, _ := api("GET", "/envelopes/"+envelopeID+"/status", nil)
fmt.Println(status["status"]) // "pending" | "completed"
fmt.Println(status["percentage"]) // 50Send a Reminder
Send a reminder email to recipients who haven't signed yet.
await api(`/envelopes/${envelopeId}/remind`, { method: "POST" });api("POST", f"/envelopes/{envelope_id}/remind")api('POST', "/envelopes/$envelopeId/remind");api("POST", "/envelopes/"+envelopeID+"/remind", nil)Cancel Signing
Cancel a pending envelope. Recipients will no longer be able to sign.
await api(`/envelopes/${envelopeId}/cancel`, { method: "POST" });api("POST", f"/envelopes/{envelope_id}/cancel")api('POST', "/envelopes/$envelopeId/cancel");api("POST", "/envelopes/"+envelopeID+"/cancel", nil)Templates
Templates let you save envelope configurations (recipients, fields, settings) for reuse.
Create a Template
const template = await api("/templates", {
method: "POST",
body: JSON.stringify({
name: "NDA Template",
description: "Standard non-disclosure agreement",
}),
});template = api("POST", "/templates", json={
"name": "NDA Template",
"description": "Standard non-disclosure agreement",
})$template = api('POST', '/templates', [
'name' => 'NDA Template',
'description' => 'Standard non-disclosure agreement',
]);template, _ := api("POST", "/templates", map[string]interface{}{
"name": "NDA Template",
"description": "Standard non-disclosure agreement",
})Create Envelope from Template
Create a new envelope pre-configured with the template's settings.
const envelope = await api(`/templates/${templateId}/create-envelope`, {
method: "POST",
body: JSON.stringify({
title: "NDA - Acme Corp",
fileName: "nda-acme.pdf",
fileType: "application/pdf",
fileSize: 34200,
}),
});
// Continue with upload → add recipients → sendenvelope = api("POST", f"/templates/{template_id}/create-envelope", json={
"title": "NDA - Acme Corp",
"fileName": "nda-acme.pdf",
"fileType": "application/pdf",
"fileSize": 34200,
})$envelope = api('POST', "/templates/$templateId/create-envelope", [
'title' => 'NDA - Acme Corp',
'fileName' => 'nda-acme.pdf',
'fileType' => 'application/pdf',
'fileSize' => 34200,
]);envelope, _ := api("POST", "/templates/"+templateID+"/create-envelope", map[string]interface{}{
"title": "NDA - Acme Corp",
"fileName": "nda-acme.pdf",
"fileType": "application/pdf",
"fileSize": 34200,
})Other Template Operations
| Action | Method | Endpoint |
|---|---|---|
| List templates | GET | /templates |
| Get template | GET | /templates/{id} |
| Update template | PATCH | /templates/{id} |
| Delete template | DELETE | /templates/{id} |
| Duplicate template | POST | /templates/{id}/duplicate |
Credits
Check Balance
const balance = await api("/credits/balance");
console.log(`${balance.available} credits available`);balance = api("GET", "/credits/balance")
print(f"{balance['available']} credits available")$balance = api('GET', '/credits/balance');
echo "{$balance['available']} credits available";balance, _ := api("GET", "/credits/balance", nil)
fmt.Printf("%.0f credits available\n", balance["available"])Get Usage Stats
const usage = await api("/credits/usage");usage = api("GET", "/credits/usage")$usage = api('GET', '/credits/usage');usage, _ := api("GET", "/credits/usage", nil)List Transactions
const transactions = await api("/credits/transactions");transactions = api("GET", "/credits/transactions")$transactions = api('GET', '/credits/transactions');transactions, _ := api("GET", "/credits/transactions", nil)Full Example: End-to-End Signing
Here is the complete flow in a single script.
import { readFileSync } from "node:fs";
const API_KEY = process.env.API_KEY;
const BASE = "https://api.signpad.signsecure.in/api/v1";
async function api(path, options = {}) {
const res = await fetch(`${BASE}${path}`, {
...options,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
async function main() {
// 1. Create envelope with workflow settings
const envelope = await api("/envelopes", {
method: "POST",
body: JSON.stringify({
fileName: "contract.pdf",
fileType: "application/pdf",
fileSize: readFileSync("contract.pdf").length,
title: "Service Agreement",
workflow: {
mode: "parallel",
verificationMethod: "email_verification",
message: "Please sign this agreement.",
emailNotifications: "all",
},
}),
});
console.log("Created:", envelope.id);
// 2. Upload PDF
await fetch(envelope.uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: readFileSync("contract.pdf"),
});
console.log("Uploaded PDF");
// 3. Add recipients
await api(`/envelopes/${envelope.id}/recipients`, {
method: "POST",
body: JSON.stringify({
recipients: [
{
name: "Jane Doe",
email: "jane@example.com",
role: "signer",
order: 1,
signatureMethod: "electronic",
},
],
}),
});
console.log("Added recipients");
// 4. Send for signing (no body needed -- workflow was set during creation)
await api(`/envelopes/${envelope.id}/send`, {
method: "POST",
});
console.log("Sent for signing");
// 5. Poll until complete
let status;
do {
await new Promise((r) => setTimeout(r, 5000));
status = await api(`/envelopes/${envelope.id}/status`);
console.log(`Progress: ${status.percentage}%`);
} while (status.status !== "completed");
// 6. Download signed PDF
const download = await api(`/envelopes/${envelope.id}/file`);
console.log("Download URL:", download.url);
}
main().catch(console.error);import os
import time
import requests
API_KEY = os.environ["API_KEY"]
BASE = "https://api.signpad.signsecure.in/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def api(method, path, **kwargs):
r = requests.request(method, f"{BASE}{path}", headers=HEADERS, **kwargs)
r.raise_for_status()
return r.json()
def main():
# 1. Create envelope with workflow settings
file_data = open("contract.pdf", "rb").read()
envelope = api("POST", "/envelopes", json={
"fileName": "contract.pdf",
"fileType": "application/pdf",
"fileSize": len(file_data),
"title": "Service Agreement",
"workflow": {
"mode": "parallel",
"verificationMethod": "email_verification",
"message": "Please sign this agreement.",
"emailNotifications": "all",
},
})
print(f"Created: {envelope['id']}")
# 2. Upload PDF
requests.put(
envelope["uploadUrl"],
headers={"Content-Type": "application/pdf"},
data=file_data,
)
print("Uploaded PDF")
# 3. Add recipients
api("POST", f"/envelopes/{envelope['id']}/recipients", json={
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "signer",
"order": 1,
"signatureMethod": "electronic",
},
]
})
print("Added recipients")
# 4. Send for signing (no body needed -- workflow was set during creation)
api("POST", f"/envelopes/{envelope['id']}/send")
print("Sent for signing")
# 5. Poll until complete
while True:
time.sleep(5)
status = api("GET", f"/envelopes/{envelope['id']}/status")
print(f"Progress: {status['percentage']}%")
if status["status"] == "completed":
break
# 6. Download signed PDF
download = api("GET", f"/envelopes/{envelope['id']}/file")
print(f"Download URL: {download['url']}")
if __name__ == "__main__":
main()For production, replace polling with Webhooks to get notified instantly when signing events occur.
Next Steps
- API Introduction -- authentication, rate limits, error handling
- Webhooks -- real-time event notifications
- Getting Started -- step-by-step first envelope tutorial
- Browse the endpoint reference in the sidebar for request/response schemas
Getting Started
Send your first envelope for signing using the SignSecure API.
Get Credit Balance GET
Get the current credit balance. The response depends on the API key's context: - **Personal API key** — returns your personal credit balance. - **Organization API key (owner/admin)** — returns the organization wallet's spendable credits and totals. - **Organization API key (member)** — returns the member's remaining allocated credits. The `context` field in the response indicates which type of balance is being returned.