Skip to Content

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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API keys can be created in your Organization Settings → API Keys.

Permissions

API keys support scoped permissions:

  • esign:read — List templates, get envelope status
  • esign:write — Create envelopes, send, cancel, remind

Base URL

https://penvio.io/api/v1

Rate Limits

TierRequests per 15 minutes
Enterprise10,000

Rate limit information is returned in response headers:

  • X-RateLimit-Limit — Maximum requests allowed
  • X-RateLimit-Remaining — Requests remaining
  • X-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/templates

Returns a paginated list of active templates.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Items per page (max 100)
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/envelopes

Creates a new envelope from a template.

Request Body

FieldTypeRequiredDescription
templateIdstringYesTemplate ID
signersarrayYesList of signers (1-20)
signers[].emailstringYesSigner email
signers[].namestringYesSigner name
signers[].rolestringNoRole (must match template)
signers[].orderintegerNoSigning order
subjectstringNoEmail subject
messagestringNoEmail message
metadataobjectNoCustom metadata
webhookUrlstringNoWebhook URL
webhookSecretstringNoWebhook secret (min 16 chars)
expiresInDaysintegerNoDays until expiration (default: 30)
signingOrderstringNoSEQUENTIAL or PARALLEL
sendImmediatelybooleanNoSend immediately (default: false)
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/envelopes

Returns a paginated list of envelopes.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Items per page (max 100)
statusstringFilter 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}/send

Sends a draft envelope to signers. Only works for envelopes in DRAFT status.

Cancel Envelope

POST /api/v1/esign/envelopes/{envelopeId}/cancel

Cancels 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}/remind

Sends 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 document
  • signedDocumentStorageKey — 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

EventDescription
envelope.sentEnvelope sent to signers
envelope.viewedSigner viewed the document
envelope.signedSigner completed signing
envelope.completedAll signers completed
envelope.declinedSigner declined
envelope.expiredEnvelope expired
envelope.cancelledEnvelope 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

CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403Insufficient permissions
NOT_FOUND404Resource not found
VALIDATION_ERROR400Invalid request body
INVALID_STATE400Invalid operation for current state
RATE_LIMITED429Rate limit exceeded
QUOTA_EXCEEDED403Monthly 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

Last updated on