> ## Documentation Index
> Fetch the complete documentation index at: https://docsautomator.co/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# eSign API Reference

> Programmatic management of signing sessions, links, and audit trails

The DocsAutomator eSign API enables programmatic management of signing sessions, retrieval of signing links, progress tracking, and audit trail access.

## Authentication

All API endpoints require bearer token authentication:

```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```

## Base URL

```
https://api.docsautomator.co/esign
```

***

## Session Management

### List Signing Sessions

<ParamField path="GET" type="/esign/sessions">
  Retrieve all signing sessions with optional filtering.
</ParamField>

**Query Parameters:**

| Parameter | Type   | Required | Description                              |
| --------- | ------ | -------- | ---------------------------------------- |
| `status`  | string | No       | Filter by status                         |
| `page`    | number | No       | Page number (default: 1)                 |
| `limit`   | number | No       | Results per page (default: 20, max: 100) |
| `email`   | string | No       | Search by signer email                   |

**Valid Status Values:** `pending`, `in_progress`, `completed`, `expired`, `cancelled`, `declined`

```bash theme={null}
curl -X GET "https://api.docsautomator.co/esign/sessions?status=pending&page=1&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**

```json theme={null}
{
  "sessions": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0d1",
      "documentName": "Invoice #123",
      "status": "in_progress",
      "signingOrder": "sequential",
      "signers": [
        { "index": 1, "email": "alice@example.com", "name": "Alice", "status": "signed" },
        { "index": 2, "email": "bob@example.com", "name": "Bob", "status": "invited" }
      ],
      "webhookParams": { "recId": "recAbC123XyZ" },
      "expiresAt": "2026-05-20T10:00:00.000Z",
      "completedAt": null,
      "createdAt": "2026-04-20T10:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 47,
    "pages": 3
  }
}
```

***

### Get Session Details

<ParamField path="GET" type="/esign/sessions/:sessionId">
  Returns comprehensive session information including signer statuses, document hashes, e-sign fields with positions, and completion timestamps.
</ParamField>

Per-signer fields include `declinedAt` and `declineReason` for any signer who declined. When the session `status` is `declined`, the response additionally includes top-level `declinedAt`, `declinedBy` (`email`, `name`, `signerIndex`), and `declineReason` so you can read refusal details without inspecting the signers array.

**Response:**

```json theme={null}
{
  "id": "66f1a2b3c4d5e6f7a8b9c0d1",
  "documentName": "Invoice #123",
  "status": "completed",
  "signingOrder": "sequential",
  "deliveryMethod": "email",
  "currentSignerIndex": 2,
  "originalPdfUrl": "https://files.docsautomator.co/f/...",
  "signedPdfUrl": "https://files.docsautomator.co/f/...",
  "originalDocumentHash": "sha256:...",
  "signedDocumentHash": "sha256:...",
  "esignFields": [
    {
      "fieldId": "signature_1",
      "type": "signature",
      "tagName": "{{esign.signature_1}}",
      "signerIndex": 1,
      "role": "buyer",
      "value": "data:image/png;base64,...",
      "signedAt": "2026-04-22T14:02:11.000Z",
      "signedBy": "alice@example.com"
    }
  ],
  "signers": [
    {
      "index": 1,
      "email": "alice@example.com",
      "name": "Alice",
      "role": "buyer",
      "status": "signed",
      "accessToken": "a1b2c3...",
      "invitedAt": "2026-04-20T10:00:00.000Z",
      "openedAt": "2026-04-22T13:55:04.000Z",
      "signedAt": "2026-04-22T14:02:11.000Z",
      "declinedAt": null,
      "declineReason": null,
      "ipAddress": "203.0.113.42",
      "consentGivenAt": "2026-04-22T13:55:30.000Z",
      "timezone": "America/New_York",
      "geoLocation": { "country": "US", "region": "NY", "city": "New York" },
      "signingDurationSeconds": 427
    }
  ],
  "reminderEnabled": true,
  "reminderDays": 3,
  "reminderCount": 0,
  "lastReminderSent": null,
  "webhookParams": { "recId": "recAbC123XyZ" },
  "expiresAt": "2026-05-20T10:00:00.000Z",
  "completedAt": "2026-04-22T14:02:45.000Z",
  "createdAt": "2026-04-20T10:00:00.000Z"
}
```

`webhookParams` echoes the custom parameters from the original document creation request, plus the identifier of the triggering record, folded in automatically per data source: `recId` (Airtable, SmartSuite, Notion — `pageId` and Notion Button payloads are normalized to `recId`), `taskId` (ClickUp), or `rowNumber` (Google Sheets — positional at trigger time, so treat it as "which row triggered this", not a durable key). Your own `webhookParams` keys are never overwritten by the folded identifier. Use it to correlate signing sessions with records in your data source — for example, when polling sessions periodically to reconcile signing status against your own records. It is `null` when no identifier or parameters were passed.

When `status` is `declined`, the response also includes:

```json theme={null}
{
  "declinedAt": "2026-04-22T14:10:00.000Z",
  "declinedBy": {
    "email": "bob@example.com",
    "name": "Bob",
    "signerIndex": 2
  },
  "declineReason": "Terms need to be revised"
}
```

***

### Get Signing Links

<ParamField path="GET" type="/esign/sessions/:sessionId/links">
  Retrieves signing links for all signers when using manual delivery mode.
</ParamField>

Returns expiration dates and signer status for each link.

**Response:**

```json theme={null}
{
  "success": true,
  "sessionId": "66f1a2b3c4d5e6f7a8b9c0d1",
  "documentName": "Invoice #123",
  "deliveryMethod": "manual",
  "status": "pending",
  "signers": [
    {
      "signerIndex": 1,
      "email": "alice@example.com",
      "name": "Alice",
      "signingLink": "https://app.docsautomator.co/sign/a1b2c3d4e5f6g7h8",
      "expiresAt": "2026-05-20T10:00:00.000Z",
      "status": "pending"
    }
  ]
}
```

***

### Get Audit Trail

<ParamField path="GET" type="/esign/sessions/:sessionId/audit">
  Returns chronological event log for the signing session.
</ParamField>

**Event Types:**

| Event               | Description                     |
| ------------------- | ------------------------------- |
| `session_created`   | Signing session initiated       |
| `invite_sent`       | Email invitation dispatched     |
| `invite_resent`     | Invitation re-sent to signer    |
| `link_opened`       | Signer accessed signing link    |
| `document_viewed`   | Signer viewed PDF               |
| `consent_given`     | E-consent provided              |
| `field_signed`      | Field value entered             |
| `field_cleared`     | Field value cleared             |
| `signer_completed`  | All fields finished             |
| `signer_declined`   | Signer declined to sign         |
| `session_completed` | All signers done, PDF generated |
| `session_cancelled` | Session cancelled               |
| `session_expired`   | Session expired                 |
| `session_voided`    | Session voided by admin         |
| `reminder_sent`     | Reminder email sent             |

**Response:**

```json theme={null}
{
  "sessionId": "66f1a2b3c4d5e6f7a8b9c0d1",
  "events": [
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0e5",
      "eventType": "session_created",
      "actorType": "system",
      "actorEmail": null,
      "actorSignerIndex": null,
      "fieldId": null,
      "fieldType": null,
      "metadata": {
        "documentName": "Invoice #123",
        "signerCount": 2,
        "signingOrder": "sequential"
      },
      "ipAddress": null,
      "userAgent": null,
      "timestamp": "2026-04-20T10:00:00.000Z",
      "timezone": null,
      "geoLocation": null
    },
    {
      "id": "66f1a2b3c4d5e6f7a8b9c0e6",
      "eventType": "field_signed",
      "actorType": "signer",
      "actorEmail": "alice@example.com",
      "actorSignerIndex": 1,
      "fieldId": "signature_1",
      "fieldType": "signature",
      "metadata": {},
      "ipAddress": "203.0.113.42",
      "userAgent": "Mozilla/5.0 ...",
      "timestamp": "2026-04-22T14:02:11.000Z",
      "timezone": "America/New_York",
      "geoLocation": { "country": "US", "region": "NY", "city": "New York" }
    }
  ],
  "reconstructed": false
}
```

<Note>
  `reconstructed: true` indicates that some events were inferred from the session document because the original audit events were missing (e.g., pruned from an older session).
</Note>

***

### Cancel Session

<ParamField path="POST" type="/esign/sessions/:sessionId/cancel">
  Cancel an active signing session.
</ParamField>

**Request body:**

```json theme={null}
{
  "reason": "Optional cancellation reason"
}
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Signing session has been cancelled"
}
```

<Warning>
  Cannot cancel completed sessions.
</Warning>

***

### Resend Invitation

<ParamField path="POST" type="/esign/sessions/:sessionId/resend/:signerIndex">
  Resends invitation email to a specific signer (indexed 1, 2, 3, etc.).
</ParamField>

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Invitation resent to alice@example.com"
}
```

***

## Webhook Events

| Event                    | When it fires                                   |
| ------------------------ | ----------------------------------------------- |
| *(no `event` field)*     | All signers completed — signed PDF ready        |
| `esign.session_created`  | Session created with `deliveryMethod: "manual"` |
| `esign.signer_signed`    | An individual signer completed all their fields |
| `esign.session_declined` | A signer declined to sign                       |
| `esign.session_expired`  | Session expired before all signers completed    |

See [eSign overview — Webhook Integration](/features/docsautomator-esign#webhook-integration) for the full payload of each event.

### Session Created (Manual Delivery)

Triggered when signing session created with `deliveryMethod: "manual"`. Use to retrieve and distribute signing links manually.

### Signing Completed

Triggered when all signers complete signing and final PDF is generated.

**Payload:**

```json theme={null}
{
  "signedPdfUrl": "https://...",
  "sessionId": "unique-identifier",
  "documentName": "Document name",
  "status": "completed",
  "completedAt": "ISO date string",
  "signers": [
    {
      "email": "signer@example.com",
      "name": "Signer Name",
      "signedAt": "ISO date string"
    }
  ],
  "fieldValues": { },
  "googleDriveFileId": "1aBcDeFgHiJkLmNoPqRsTuVwXyZ",
  "googleDriveUrl": "https://drive.google.com/file/d/1aBcDeFgHiJkLmNoPqRsTuVwXyZ/view",
  "googleDriveFolderId": "0BxAbCdEfGhIjKlMnOpQrStUvWx",
  "sourceData": { },
  "webhookParams": { },
  "additionalParams": { }
}
```

<Note>
  The `googleDriveFileId`, `googleDriveUrl`, and `googleDriveFolderId` fields are only present when "Save to Google Drive" is enabled in the Signed PDF Delivery settings.
</Note>

### Signer Signed

Fires once per signer as they complete, including the final signer. Use to track progress for multi-party documents.

```json theme={null}
{
  "event": "esign.signer_signed",
  "sessionId": "unique-identifier",
  "documentName": "Document name",
  "status": "in_progress",
  "signer": {
    "email": "signer@example.com",
    "name": "Signer Name",
    "signerIndex": 1,
    "role": "buyer",
    "signedAt": "ISO date string"
  },
  "progress": {
    "completed": 1,
    "total": 2,
    "allSigned": false
  },
  "sourceData": { },
  "webhookParams": { },
  "additionalParams": { }
}
```

<Note>
  For the final signer, `progress.allSigned` is `true` but the session `status` is still `in_progress`. The separate `Signing Completed` webhook fires afterwards, once the signed PDF is ready.
</Note>

### Session Expired

Fires when a signing session expires before all signers complete. An expiration email is also sent to workspace notification recipients.

```json theme={null}
{
  "event": "esign.session_expired",
  "sessionId": "unique-identifier",
  "documentName": "Document name",
  "status": "expired",
  "expiredAt": "ISO date string",
  "expiresAt": "ISO date string",
  "pendingSigners": [
    {
      "email": "signer@example.com",
      "name": "Signer Name",
      "signerIndex": 2,
      "status": "invited"
    }
  ],
  "sourceData": { },
  "webhookParams": { },
  "additionalParams": { }
}
```

<Note>
  If a session is already declined, cancelled, or completed before its expiration time, this webhook does not fire.
</Note>

### Passing Additional Parameters

Include `webhookParams` in document creation requests to receive custom data in webhook notifications:

```json theme={null}
{
  "docId": "your-automation-id",
  "data": { ... },
  "webhookParams": {
    "orderId": "ORD-12345",
    "customerId": "CUST-789"
  }
}
```

Parameters are returned in all webhook notifications under `webhookParams`, together with the automatically included trigger record identifier (`recId`, `taskId`, or `rowNumber` depending on the data source). An identical `additionalParams` key is also present — it exists only for legacy reasons, so use `webhookParams` in new integrations.

***

## Status Reference

### Session Statuses

| Status        | Description                 |
| ------------- | --------------------------- |
| `pending`     | Awaiting signer action      |
| `in_progress` | At least one signer started |
| `completed`   | All signers finished        |
| `expired`     | Expired before completion   |
| `cancelled`   | Cancelled by admin          |
| `declined`    | Signer declined             |

### Signer Statuses

| Status     | Description          |
| ---------- | -------------------- |
| `pending`  | Invitation not sent  |
| `invited`  | Email sent           |
| `opened`   | Link opened          |
| `signed`   | All fields completed |
| `declined` | Declined to sign     |

***

## Error Responses

```json theme={null}
{
  "error": true,
  "message": "Description"
}
```

| Code | Description                            |
| ---- | -------------------------------------- |
| 400  | Bad request — invalid parameters       |
| 401  | Unauthorized — invalid/missing API key |
| 404  | Not found — session doesn't exist      |
| 429  | Rate limited                           |
| 500  | Server error                           |

***

## Rate Limits

Rate limits apply to the public signing page routes (not the authenticated admin API endpoints):

| Route               | Limit                              |
| ------------------- | ---------------------------------- |
| Signing page access | 100 requests per 15 minutes per IP |
| Field submissions   | 30 requests per minute per IP      |

***

## Code Examples

### Node.js — List and Monitor Sessions

```javascript theme={null}
const axios = require('axios');

const API_KEY = 'your_api_key';
const BASE_URL = 'https://api.docsautomator.co/esign';

async function listPendingSessions() {
  const response = await axios.get(`${BASE_URL}/sessions`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
    params: { status: 'pending', limit: 50 }
  });
  return response.data.sessions;
}

async function getSessionWithAudit(sessionId) {
  const [session, audit] = await Promise.all([
    axios.get(`${BASE_URL}/sessions/${sessionId}`, {
      headers: { Authorization: `Bearer ${API_KEY}` }
    }),
    axios.get(`${BASE_URL}/sessions/${sessionId}/audit`, {
      headers: { Authorization: `Bearer ${API_KEY}` }
    })
  ]);

  return {
    ...session.data,
    auditTrail: audit.data.events
  };
}
```

### Python — Webhook Handler

```python theme={null}
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook/esign', methods=['POST'])
def handle_esign_webhook():
    payload = request.json

    if payload.get('event') == 'esign.session_created':
        for signer in payload.get('signingLinks', []):
            send_custom_email(
                to=signer['email'],
                signing_link=signer['signingLink']
            )

    elif payload.get('status') == 'completed':
        signed_pdf_url = payload['signedPdfUrl']
        order_id = payload.get('webhookParams', {}).get('orderId')
        drive_url = payload.get('googleDriveUrl')  # Present if Save to Drive is enabled
        process_signed_document(signed_pdf_url, order_id, drive_url=drive_url)

    return jsonify({'received': True})
```
