SignSecure Sign PadSignSecure Sign Pad

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 PDF

For 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 URL
envelope, _ := 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 objects
envelopes = 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

RoleDescription
signerMust sign the document
approverMust approve (no signature)
ccReceives a copy only
MethodCost (default tier)DescriptionText Placement
electronic10 credits per signerDraw or type a signatureSupported
aadhaar_otp20 credits per signerAadhaar eSign with OTPSupported (auto-resolved to coordinates)
dsc_usb10 credits per signerUSB digital certificateNot supported (coordinates only)
allPer method chosenRecipient chooses at signingSupported (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:

ValueBehavior
allWhatsApp messages at every step: signing requests, next-signer alerts, reminders, and completion
completion_onlyOnly 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

TypeDescription
signatureSignature capture area
textSingle-line text input
textareaMulti-line text input
emailEmail address field
numberNumeric input
dateDate picker
checkboxCheckbox
radioRadio button group
dropdownDropdown selection

Field Schema

Common properties for every field:

PropertyTypeNotes
typeenumRequired. One of the field types above
fieldKeystringRequired. Unique key within the envelope
pageNumberintegerRequired. 1-indexed page
x, ynumberRequired. PDF points from the top-left corner (stored with 0.01-point precision)
width, heightnumberRequired. Field box size in PDF points
labelstringField label. Required for checkboxes when showLabel is true
requiredbooleanDefault false
readOnlybooleanDefault false
recipientIdstringAssigns the field to a recipient on this envelope
defaultValuestring | number | booleanPre-filled value
placeholderstringPlaceholder text shown while filling
optionsstring[]Choices for dropdown fields
validationobject{ 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):

PropertyTypeNotes
fontFamilyenumsans-serif, serif, or monospace
fontWeightenumnormal or bold
fontStyleenumnormal or italic
fontSizenumber6-72 points. Auto-sized to the field box when omitted
textColorstringHex color, e.g. #1a1a1a
multilinebooleanRender text/textarea values as a multi-line box

Checkbox properties:

PropertyTypeNotes
showLabelbooleanRender the label next to the checkbox (a label is then required)
checkboxSizenumberCheckbox glyph size in PDF points. Defaults to the field height when showLabel is true, otherwise to min(width, height)
checkboxLabelFontSizenumberLabel font size, 8-24

Radio group configuration (radioGroup, required for radio fields):

PropertyTypeNotes
optionsarray{ value, label?, pageNumber?, x?, y?, width?, height? } -- per-option positions are used only in MANUAL layout. label defaults to value
layoutenumAUTO (default -- options laid out from the field origin) or MANUAL (per-option positions)
directionenumVERTICAL or HORIZONTAL stacking for AUTO layout
spacingnumberDistance between the origins of consecutive options (top-to-top stride vertically, left-to-left horizontally) -- not the gap between them
radioSizenumberRadio button diameter in PDF points
labelFontSizenumberFont 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"]) // 50

Send 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 → send
envelope = 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

ActionMethodEndpoint
List templatesGET/templates
Get templateGET/templates/{id}
Update templatePATCH/templates/{id}
Delete templateDELETE/templates/{id}
Duplicate templatePOST/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

On this page