Complete Workflow
End-to-end API integration covering every action from setup to signed document retrieval.
This guide covers the complete SignSecure Sign Pad 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",
phone: "+919876543210", // optional — enables WhatsApp delivery
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",
"phone": "+919876543210", # optional — enables WhatsApp delivery
"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',
'phone' => '+919876543210', // optional — enables WhatsApp delivery
'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", "phone": "+919876543210", "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 (default tier) | Description | Text Placement |
|---|---|---|---|
electronic | 10 credits per signer | Draw or type a signature | Supported |
aadhaar_otp | 20 credits per signer | Aadhaar eSign with OTP | Supported (auto-resolved to coordinates) |
dsc_usb | 10 credits per signer | USB digital certificate | Not supported (coordinates only) |
all | Per method chosen | Recipient chooses at signing | Supported (coordinates recommended) |
Every envelope also consumes a one-time 1-credit base charge. Rates come from your account's assigned pricing tier and are locked into a per-envelope snapshot at send time -- see Get Credit Balance for the credit API.
Contact Details and WhatsApp
Each recipient needs a name plus at least one contact method: an email, a phone, or both. phone must be in E.164 format (for example +919876543210) and enables WhatsApp delivery for that recipient.
Whether WhatsApp messages are actually sent is controlled by the envelope workflow's whatsappNotifications preference, set when creating the envelope:
| Value | Behavior |
|---|---|
all | WhatsApp messages at every step: signing requests, next-signer alerts, reminders, and completion |
completion_only | Only a completion message when everyone has signed |
none (default) | No WhatsApp notifications |
await api("/envelopes", {
method: "POST",
body: JSON.stringify({
fileName: "contract.pdf",
title: "Service Agreement",
workflow: {
mode: "sequential",
emailNotifications: "all",
whatsappNotifications: "all",
},
}),
});Recipients without a phone are skipped for WhatsApp and receive email only (and vice versa for phone-only recipients).
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: [
{
type: "text",
fieldKey: "full_name",
recipientId: "rec_abc123",
pageNumber: 1,
x: 100, y: 520, width: 200, height: 30,
label: "Full name",
required: true,
// Text styling — honored when the value is rendered into the PDF
fontFamily: "serif",
fontWeight: "bold",
fontSize: 12,
textColor: "#1a1a1a",
validation: { maxLength: 60 },
},
{
type: "checkbox",
fieldKey: "accept_terms",
recipientId: "rec_abc123",
pageNumber: 1,
x: 100, y: 560, width: 180, height: 20,
label: "I accept the terms",
required: true,
showLabel: true,
checkboxSize: 14,
checkboxLabelFontSize: 10,
},
{
type: "radio",
fieldKey: "payment_plan",
recipientId: "rec_abc123",
pageNumber: 1,
x: 100, y: 590, width: 160, height: 60,
label: "Payment plan",
radioGroup: {
options: [
{ value: "monthly", label: "Monthly" },
{ value: "yearly", label: "Yearly" },
],
layout: "AUTO",
direction: "VERTICAL",
spacing: 22, // top-to-top stride between options
radioSize: 12,
labelFontSize: 10,
},
},
{
type: "signature",
fieldKey: "signature_1",
recipientId: "rec_abc123",
pageNumber: 1,
x: 100, y: 680, width: 200, height: 50,
required: true,
},
],
}),
});fields = api("POST", f"/envelopes/{envelope_id}/fields", json={
"fields": [
{
"type": "text",
"fieldKey": "full_name",
"recipientId": "rec_abc123",
"pageNumber": 1,
"x": 100, "y": 520,
"width": 200, "height": 30,
"label": "Full name",
"required": True,
# Text styling — honored when the value is rendered into the PDF
"fontFamily": "serif",
"fontWeight": "bold",
"fontSize": 12,
"textColor": "#1a1a1a",
"validation": {"maxLength": 60},
},
{
"type": "checkbox",
"fieldKey": "accept_terms",
"recipientId": "rec_abc123",
"pageNumber": 1,
"x": 100, "y": 560,
"width": 180, "height": 20,
"label": "I accept the terms",
"required": True,
"showLabel": True,
"checkboxSize": 14,
"checkboxLabelFontSize": 10,
},
{
"type": "radio",
"fieldKey": "payment_plan",
"recipientId": "rec_abc123",
"pageNumber": 1,
"x": 100, "y": 590,
"width": 160, "height": 60,
"label": "Payment plan",
"radioGroup": {
"options": [
{"value": "monthly", "label": "Monthly"},
{"value": "yearly", "label": "Yearly"},
],
"layout": "AUTO",
"direction": "VERTICAL",
"spacing": 22, # top-to-top stride between options
"radioSize": 12,
"labelFontSize": 10,
},
},
{
"type": "signature",
"fieldKey": "signature_1",
"recipientId": "rec_abc123",
"pageNumber": 1,
"x": 100, "y": 680,
"width": 200, "height": 50,
"required": True,
},
]
})$fields = api('POST', "/envelopes/$envelopeId/fields", [
'fields' => [
[
'type' => 'text',
'fieldKey' => 'full_name',
'recipientId' => 'rec_abc123',
'pageNumber' => 1,
'x' => 100, 'y' => 520,
'width' => 200, 'height' => 30,
'label' => 'Full name',
'required' => true,
// Text styling — honored when the value is rendered into the PDF
'fontFamily' => 'serif',
'fontWeight' => 'bold',
'fontSize' => 12,
'textColor' => '#1a1a1a',
'validation' => ['maxLength' => 60],
],
[
'type' => 'checkbox',
'fieldKey' => 'accept_terms',
'recipientId' => 'rec_abc123',
'pageNumber' => 1,
'x' => 100, 'y' => 560,
'width' => 180, 'height' => 20,
'label' => 'I accept the terms',
'required' => true,
'showLabel' => true,
'checkboxSize' => 14,
'checkboxLabelFontSize' => 10,
],
[
'type' => 'radio',
'fieldKey' => 'payment_plan',
'recipientId' => 'rec_abc123',
'pageNumber' => 1,
'x' => 100, 'y' => 590,
'width' => 160, 'height' => 60,
'label' => 'Payment plan',
'radioGroup' => [
'options' => [
['value' => 'monthly', 'label' => 'Monthly'],
['value' => 'yearly', 'label' => 'Yearly'],
],
'layout' => 'AUTO',
'direction' => 'VERTICAL',
'spacing' => 22, // top-to-top stride between options
'radioSize' => 12,
'labelFontSize' => 10,
],
],
[
'type' => 'signature',
'fieldKey' => 'signature_1',
'recipientId' => 'rec_abc123',
'pageNumber' => 1,
'x' => 100, 'y' => 680,
'width' => 200, 'height' => 50,
'required' => true,
],
],
]);fields, _ := api("POST", "/envelopes/"+envelopeID+"/fields", map[string]interface{}{
"fields": []map[string]interface{}{
{
"type": "text", "fieldKey": "full_name", "recipientId": "rec_abc123",
"pageNumber": 1, "x": 100, "y": 520, "width": 200, "height": 30,
"label": "Full name", "required": true,
"fontFamily": "serif", "fontWeight": "bold", "fontSize": 12, "textColor": "#1a1a1a",
"validation": map[string]interface{}{"maxLength": 60},
},
{
"type": "checkbox", "fieldKey": "accept_terms", "recipientId": "rec_abc123",
"pageNumber": 1, "x": 100, "y": 560, "width": 180, "height": 20,
"label": "I accept the terms", "required": true,
"showLabel": true, "checkboxSize": 14, "checkboxLabelFontSize": 10,
},
{
"type": "radio", "fieldKey": "payment_plan", "recipientId": "rec_abc123",
"pageNumber": 1, "x": 100, "y": 590, "width": 160, "height": 60,
"label": "Payment plan",
"radioGroup": map[string]interface{}{
"options": []map[string]interface{}{
{"value": "monthly", "label": "Monthly"},
{"value": "yearly", "label": "Yearly"},
},
"layout": "AUTO", "direction": "VERTICAL",
"spacing": 22, "radioSize": 12, "labelFontSize": 10,
},
},
{
"type": "signature", "fieldKey": "signature_1", "recipientId": "rec_abc123",
"pageNumber": 1, "x": 100, "y": 680, "width": 200, "height": 50,
"required": true,
},
},
})By default replaceExisting is true, so each call replaces the envelope's entire field list. Pass "replaceExisting": false to append instead.
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 |
Field Schema
Common properties for every field:
| Property | Type | Notes |
|---|---|---|
type | enum | Required. One of the field types above |
fieldKey | string | Required. Unique key within the envelope |
pageNumber | integer | Required. 1-indexed page |
x, y | number | Required. PDF points from the top-left corner (stored with 0.01-point precision) |
width, height | number | Required. Field box size in PDF points |
label | string | Field label. Required for checkboxes when showLabel is true |
required | boolean | Default false |
readOnly | boolean | Default false |
recipientId | string | Assigns the field to a recipient on this envelope |
defaultValue | string | number | boolean | Pre-filled value |
placeholder | string | Placeholder text shown while filling |
options | string[] | Choices for dropdown fields |
validation | object | { min, max, pattern, minLength, maxLength } -- maxLength is also enforced by the PDF engine |
Text styling (applied when the filled value is rendered into the final PDF):
| Property | Type | Notes |
|---|---|---|
fontFamily | enum | sans-serif, serif, or monospace |
fontWeight | enum | normal or bold |
fontStyle | enum | normal or italic |
fontSize | number | 6-72 points. Auto-sized to the field box when omitted |
textColor | string | Hex color, e.g. #1a1a1a |
multiline | boolean | Render text/textarea values as a multi-line box |
Checkbox properties:
| Property | Type | Notes |
|---|---|---|
showLabel | boolean | Render the label next to the checkbox (a label is then required) |
checkboxSize | number | Checkbox glyph size in PDF points. Defaults to the field height when showLabel is true, otherwise to min(width, height) |
checkboxLabelFontSize | number | Label font size, 8-24 |
Radio group configuration (radioGroup, required for radio fields):
| Property | Type | Notes |
|---|---|---|
options | array | { value, label?, pageNumber?, x?, y?, width?, height? } -- per-option positions are used only in MANUAL layout. label defaults to value |
layout | enum | AUTO (default -- options laid out from the field origin) or MANUAL (per-option positions) |
direction | enum | VERTICAL or HORIZONTAL stacking for AUTO layout |
spacing | number | Distance between the origins of consecutive options (top-to-top stride vertically, left-to-left horizontally) -- not the gap between them |
radioSize | number | Radio button diameter in PDF points |
labelFontSize | number | Font size of the option labels rendered by the PDF engine |
List Form Fields
Returns every field with its full schema -- placement, assignment, validation, and all styling properties (fontFamily, fontWeight, fontStyle, fontSize, textColor, multiline, showLabel, checkboxSize, checkboxLabelFontSize, radioGroup).
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