E-Sign API v1 (Enterprise)
The E-Sign API v1 provides programmatic access to e-signature workflows for Enterprise customers.
API access requires a Business plan or higher.
Authentication
All requests must include an API key in the Authorization header:
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAPI keys can be created in your Organization Settings → API Keys.
Permissions
API keys support scoped permissions:
esign:read— List templates, get envelope statusesign:write— Create envelopes, send, cancel, remind
Base URL
https://penvio.io/api/v1Rate Limits
| Tier | Requests per 15 minutes |
|---|---|
| Enterprise | 10,000 |
Rate limit information is returned in response headers:
X-RateLimit-Limit— Maximum requests allowedX-RateLimit-Remaining— Requests remainingX-RateLimit-Reset— Unix timestamp when limit resets
Templates
Templates define document fields and their positions. Create templates in the Penvio dashboard, then use the API to create envelopes from them.
List Templates
GET /api/v1/esign/templatesReturns a paginated list of active templates.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
pageSize | integer | 20 | Items per page (max 100) |
cURL
curl -X GET 'https://penvio.io/api/v1/esign/templates?page=1&pageSize=20' \
-H 'Authorization: Bearer pk_live_your_api_key'Response
{
"data": [
{
"id": "tmpl_abc123",
"name": "Sales Agreement",
"description": "Standard sales agreement template",
"pageCount": 5,
"fields": [
{
"fieldId": "sig_1",
"type": "SIGNATURE",
"label": "Buyer Signature",
"required": true,
"page": 5
}
],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T14:45:00Z"
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 20,
"total": 15,
"totalPages": 1
}
}
}Get Template
GET /api/v1/esign/templates/{templateId}Returns details of a specific template.
Response
{
"data": {
"id": "tmpl_abc123",
"name": "Sales Agreement",
"description": "Standard sales agreement template",
"pageCount": 5,
"fields": [
{
"fieldId": "sig_buyer",
"type": "SIGNATURE",
"label": "Buyer Signature",
"required": true,
"page": 5
},
{
"fieldId": "date_buyer",
"type": "DATE",
"label": "Date",
"required": true,
"page": 5
}
],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T14:45:00Z"
}
}Envelopes
Envelopes are signing requests created from templates.
Create Envelope
POST /api/v1/esign/envelopesCreates a new envelope from a template.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | Yes | Template ID |
signers | array | Yes | List of signers (1-20) |
signers[].email | string | Yes | Signer email |
signers[].name | string | Yes | Signer name |
signers[].role | string | No | Role (must match template) |
signers[].order | integer | No | Signing order |
subject | string | No | Email subject |
message | string | No | Email message |
metadata | object | No | Custom metadata |
webhookUrl | string | No | Webhook URL |
webhookSecret | string | No | Webhook secret (min 16 chars) |
expiresInDays | integer | No | Days until expiration (default: 30) |
signingOrder | string | No | SEQUENTIAL or PARALLEL |
sendImmediately | boolean | No | Send immediately (default: false) |
cURL
curl -X POST 'https://penvio.io/api/v1/esign/envelopes' \
-H 'Authorization: Bearer pk_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "tmpl_abc123",
"signers": [
{
"email": "john@example.com",
"name": "John Doe",
"role": "Buyer",
"order": 1
}
],
"subject": "Please sign: Sales Agreement",
"message": "Please review and sign the attached agreement.",
"metadata": {
"orderId": "PO-12345"
},
"expiresInDays": 14,
"sendImmediately": true
}'Response (201 Created)
{
"data": {
"id": "env_def456",
"templateId": "tmpl_abc123",
"templateName": "Sales Agreement",
"status": "SENT",
"signingOrder": "SEQUENTIAL",
"subject": "Please sign: Sales Agreement",
"message": "Please review and sign the attached agreement.",
"signers": [
{
"id": "signer_xyz789",
"email": "john@example.com",
"name": "John Doe",
"role": "Buyer",
"order": 1,
"status": "SENT",
"signedAt": null,
"declinedAt": null,
"declineReason": null
}
],
"metadata": { "orderId": "PO-12345" },
"webhookUrl": null,
"expiresAt": "2024-02-01T00:00:00Z",
"createdAt": "2024-01-18T10:30:00Z",
"sentAt": "2024-01-18T10:30:00Z",
"completedAt": null,
"documentStorageKey": "esign/envelopes/org_abc/env_def456/document.pdf",
"signedDocumentStorageKey": null,
"certificateStorageKey": null
}
}List Envelopes
GET /api/v1/esign/envelopesReturns a paginated list of envelopes.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
pageSize | integer | 20 | Items per page (max 100) |
status | string | — | Filter by status |
Status Values: DRAFT, SENT, IN_PROGRESS, COMPLETED, CANCELLED, EXPIRED, DECLINED
Get Envelope
GET /api/v1/esign/envelopes/{envelopeId}Returns the current status of an envelope.
Send Envelope
POST /api/v1/esign/envelopes/{envelopeId}/sendSends a draft envelope to signers. Only works for envelopes in DRAFT status.
Cancel Envelope
POST /api/v1/esign/envelopes/{envelopeId}/cancelCancels an active envelope. Cannot cancel COMPLETED, CANCELLED, or EXPIRED envelopes.
Optional Request Body
{
"reason": "Contract terms changed"
}Send Reminder
POST /api/v1/esign/envelopes/{envelopeId}/remindSends a reminder email to pending signers.
Optional Request Body
{
"signerId": "signer_xyz789"
}If signerId is omitted, reminders are sent to all pending signers.
Retrieving Documents (BYOB)
Completed documents are stored in your BYOB bucket. The envelope response includes storage keys:
documentStorageKey— Original documentsignedDocumentStorageKey— Signed document (after completion)certificateStorageKey— Certificate of completion
Use these keys with your AWS SDK to retrieve documents:
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: 'us-east-1' });
const response = await s3.send(new GetObjectCommand({
Bucket: 'your-byob-bucket',
Key: envelope.signedDocumentStorageKey,
}));
const signedPdf = await response.Body.transformToByteArray();Webhooks
Configure a webhook URL when creating envelopes to receive real-time notifications.
Events
| Event | Description |
|---|---|
envelope.sent | Envelope sent to signers |
envelope.viewed | Signer viewed the document |
envelope.signed | Signer completed signing |
envelope.completed | All signers completed |
envelope.declined | Signer declined |
envelope.expired | Envelope expired |
envelope.cancelled | Envelope cancelled |
Webhook Payload
{
"event": "envelope.signed",
"timestamp": "2024-01-25T14:30:00Z",
"data": {
"envelopeId": "env_def456",
"status": "IN_PROGRESS",
"signer": {
"id": "signer_xyz789",
"email": "john@example.com",
"name": "John Doe",
"signedAt": "2024-01-25T14:30:00Z"
}
}
}Signature Verification
If you provided a webhookSecret, verify the signature in the X-Penvio-Signature header:
import crypto from 'crypto';
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Error Handling
All errors follow a standard format:
{
"error": {
"code": "NOT_FOUND",
"message": "Envelope not found"
}
}Error Codes
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid request body |
INVALID_STATE | 400 | Invalid operation for current state |
RATE_LIMITED | 429 | Rate limit exceeded |
QUOTA_EXCEEDED | 403 | Monthly envelope limit reached |
Complete Example
Create an API Key
Go to Organization Settings → API Keys and create a new key with esign:read and esign:write permissions.
Find a Template
curl -X GET 'https://penvio.io/api/v1/esign/templates' \
-H 'Authorization: Bearer pk_live_your_api_key'Create and Send Envelope
curl -X POST 'https://penvio.io/api/v1/esign/envelopes' \
-H 'Authorization: Bearer pk_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "tmpl_abc123",
"signers": [{"email": "john@example.com", "name": "John Doe"}],
"sendImmediately": true
}'Poll for Status (or use Webhooks)
curl -X GET 'https://penvio.io/api/v1/esign/envelopes/env_def456' \
-H 'Authorization: Bearer pk_live_your_api_key'Download Signed Document
Once status is COMPLETED, use the signedDocumentStorageKey to download from your BYOB bucket.
Next Steps
- Webhooks - Receive real-time event notifications
- Authentication - Manage API keys and permissions
- E-Sign Templates - Create templates in the dashboard
- E-Sign Envelopes - Learn about the envelope workflow