SignSecureSignSecure Docs

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_here

2. 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); // 2
import 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']; // 2
package 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

RoleDescription
signerMust sign the envelope
approverMust approve (no signature required)
ccReceives a copy, no action needed

Signature Methods

MethodDescription
electronicDraw or type a signature (free)
aadhaar_otpAadhaar eSign with OTP verification (costs credits)
dsc_usbDigital Signature Certificate via USB token (free)
allRecipient chooses at signing time

Workflow Options

OptionValuesDescription
modesequential, parallelSequential = recipients sign in order. Parallel = all sign at once
verificationMethodemail_verification, link_onlyEmail verification = recipient verifies via code. Link only = direct link
emailNotificationsall, completion_only, noneControls when recipients get notified
redirectUrlHTTPS URLOptional. 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.pdf
import { 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 hour
response = 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 hour
req, _ := 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 hour

Response:

{
  "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

On this page