Compare commits

...
Author SHA1 Message Date
ephraimduncan 3c9c490505 docs(getting-started): fix distribute route, pagination and retry advice
- POST /envelope/{id}/distribute does not exist; use POST /envelope/distribute with body
- list responses are flat (data, count, currentPage, perPage, totalPages), not nested pagination
- replace fixed 60s sleep advice with Retry-After header handling
2026-07-30 22:04:01 +00:00
ephraimduncan 905e68fdea fix(trpc): map v2 field update coordinates to service names
- ZUpdateEnvelopeFieldsRequestSchema accepts page/positionX/positionY but the service consumes
  pageNumber/pageX/pageY, so position updates via POST /envelope/field/update-many were
  silently dropped; map the names in the route before calling updateEnvelopeFields
- docs: show Decimal coordinates as serialized strings in field responses
- docs: include the defaulted fieldMeta values returned when the request omits fieldMeta
- docs: use real envelope/envelope-item ID formats in samples
2026-07-30 22:04:01 +00:00
ephraimduncan ced5af4d5a docs(api): rewrite fields examples for envelope field schemas
- request is { envelopeId, data } not { documentId, fields }
- coordinates are page/positionX/positionY not pageNumber/pageX/pageY
- responses use the data wrapper; field type samples verified against the Zod schemas
2026-07-30 22:04:00 +00:00
ephraimduncan b076a70d98 docs(api): document cancel endpoint and fix get-many body shape
- add Cancel Document section: PENDING-only, not idempotent, access rules, webhook and emails
- replace fabricated envelopeIds get-many body with the real nested ids selector (max 20)
- document the data wrapper and silent filtering of inaccessible IDs
- add CANCELLED to status table, state diagram, transitions and filters
- replace nonexistent API source value with the real enum; fix pagination shape and fences
- warn in migration guide that get-many body shape changed from documentIds
2026-07-30 22:04:00 +00:00
ephraimduncan 6ace46fefd docs(trpc): add openapi descriptions to envelope cancel, delete and update routes
- these routes rendered without descriptions in the generated API reference
- add route-level descriptions and field-level .describe() calls matching sibling schemas
2026-07-30 22:04:00 +00:00
ephraimduncan 478229aa90 docs(api): address rate limit review findings
- document the real 429 bodies: v1 returns { message }, v2 returns the structured error object
- exclude CORS preflight responses from the header guarantee
- describe monthly quotas as organisation-wide api/document/email counters, not envelope-only
- retry example: honor Retry-After exactly; cap only the exponential fallback delay
2026-07-30 22:04:00 +00:00
ephraimduncan d3eb0c7999 docs(api): document rate limit headers and 429 variants
- document X-RateLimit-Limit/-Remaining/-Reset on every v1/v2/v2-beta response
- document Retry-After on 429s and epoch-aligned 1-minute windows (real wait is 1-60s)
- show both 429 body shapes: global error key vs AppError code/message/statusCode
- cover the three 429 sources including the headerless monthly quota; add v2-beta to scope
2026-07-30 22:04:00 +00:00
ephraimduncan 918e42b992 docs(webhooks): address review findings
- qualify the SSRF guard as best-effort (no DNS-rebinding coverage, fails open on lookup errors)
- use envelope IDs from the actual generator alphabet (no digits possible)
- fix template-events intro: templateId is null except on TEMPLATE_USED
2026-07-30 21:57:43 +00:00
ephraimduncan f21eddef19 docs(webhooks): correct retry policy, timeout and payload reference
- replace fabricated retry schedule with provider behavior (local 4, BullMQ 3, Inngest 5 attempts)
- fix webhook timeout from 30s to 10s and define failure semantics (3xx not followed, code 0)
- clarify failed deliveries never auto-disable a webhook; document SSRF rules and http:// support
- add envelopeId to all payload examples; frame numeric id as legacy v1 identifier
- remove phantom documentMeta field; fix hardcoded timezone/dateFormat values
- add missing status/source enum values and document the RECIPIENT_EXPIRED event
2026-07-30 21:37:24 +00:00
14 changed files with 539 additions and 216 deletions
@@ -32,9 +32,9 @@ A document object contains the following properties:
| --------------- | -------------- | -------------------------------------------------------------- | | --------------- | -------------- | -------------------------------------------------------------- |
| `id` | string | Unique identifier (e.g., `envelope_abc123`) | | `id` | string | Unique identifier (e.g., `envelope_abc123`) |
| `type` | string | `DOCUMENT` or `TEMPLATE` | | `type` | string | `DOCUMENT` or `TEMPLATE` |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED`, or `REJECTED` | | `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED`, or `CANCELLED` |
| `title` | string | Document title | | `title` | string | Document title |
| `source` | string | How the document was created: `DOCUMENT`, `TEMPLATE`, `API` | | `source` | string | How the document was created: `DOCUMENT`, `TEMPLATE`, `TEMPLATE_DIRECT_LINK` |
| `visibility` | string | Who can view: `EVERYONE`, `ADMIN`, `MANAGER_AND_ABOVE` | | `visibility` | string | Who can view: `EVERYONE`, `ADMIN`, `MANAGER_AND_ABOVE` |
| `externalId` | string \| null | Your custom identifier for the document | | `externalId` | string \| null | Your custom identifier for the document |
| `createdAt` | string | ISO 8601 timestamp | | `createdAt` | string | ISO 8601 timestamp |
@@ -53,7 +53,7 @@ A document object contains the following properties:
"id": "envelope_abc123xyz", "id": "envelope_abc123xyz",
"type": "DOCUMENT", "type": "DOCUMENT",
"status": "PENDING", "status": "PENDING",
"source": "API", "source": "DOCUMENT",
"visibility": "EVERYONE", "visibility": "EVERYONE",
"title": "Service Agreement", "title": "Service Agreement",
"externalId": "contract-2025-001", "externalId": "contract-2025-001",
@@ -73,13 +73,13 @@ A document object contains the following properties:
], ],
"fields": [ "fields": [
{ {
"id": "field_123", "id": 123,
"type": "SIGNATURE", "type": "SIGNATURE",
"page": 1, "page": 1,
"positionX": 10, "positionX": "10",
"positionY": 80, "positionY": "80",
"width": 30, "width": "30",
"height": 5, "height": "5",
"recipientId": 1 "recipientId": 1
} }
], ],
@@ -99,6 +99,8 @@ A document object contains the following properties:
} }
``` ```
Field position and size values are stored as decimals and serialized as strings in API responses.
## List Documents ## List Documents
Retrieve a paginated list of documents. Retrieve a paginated list of documents.
@@ -114,7 +116,7 @@ GET /envelope
| `page` | integer | Page number (default: 1) | | `page` | integer | Page number (default: 1) |
| `perPage` | integer | Results per page (default: 10, max: 100) | | `perPage` | integer | Results per page (default: 10, max: 100) |
| `type` | string | Filter by `DOCUMENT` or `TEMPLATE` | | `type` | string | Filter by `DOCUMENT` or `TEMPLATE` |
| `status` | string | Filter by status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED` | | `status` | string | Filter by status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED`, `CANCELLED` |
| `source` | string | Filter by creation source | | `source` | string | Filter by creation source |
| `folderId` | string | Filter by folder ID | | `folderId` | string | Filter by folder ID |
| `orderByColumn` | string | Sort field (only `createdAt` supported) | | `orderByColumn` | string | Sort field (only `createdAt` supported) |
@@ -154,8 +156,8 @@ const response = await fetch(`${BASE_URL}/envelope`, {
}, },
}); });
const { data, pagination } = await response.json(); const { data, count } = await response.json();
console.log(`Found ${pagination.totalItems} documents`); console.log(`Found ${count} documents`);
// Filter by status // Filter by status
const pendingResponse = await fetch( const pendingResponse = await fetch(
@@ -197,12 +199,10 @@ const pendingDocs = await pendingResponse.json();
] ]
} }
], ],
"pagination": { "count": 42,
"page": 1, "currentPage": 1,
"perPage": 10, "perPage": 10,
"totalPages": 5, "totalPages": 5
"totalItems": 42
}
} }
``` ```
@@ -628,6 +628,72 @@ The response includes signing URLs for each recipient:
--- ---
## Cancel Document
Cancel a pending document. This changes its status from `PENDING` to `CANCELLED`.
```
POST /envelope/cancel
```
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------------- |
| `envelopeId` | string | Yes | Document ID |
| `reason` | string | No | Reason for cancelling the document |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/cancel" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123",
"reason": "The agreement is no longer needed."
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/cancel', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'envelope_abc123',
reason: 'The agreement is no longer needed.',
}),
});
const { success } = await response.json();
```
</Tab>
</Tabs>
### Response
```json
{
"success": true
}
```
### Behavior
- Only documents in `PENDING` status can be cancelled. Other statuses return `400`.
- Cancellation is not idempotent. Cancelling the same document again returns `400`.
- The document owner and team members with `MANAGER` or higher permissions can cancel it. Requests for documents you cannot view return `404`; requests for visible documents without sufficient permissions return `401`.
- A successful cancellation fires the `DOCUMENT_CANCELLED` webhook.
- Cancellation emails are sent only to eligible non-CC, non-rejected recipients who were sent or opened the document.
---
## Delete Document ## Delete Document
Delete a document. Completed documents cannot be deleted. Delete a document. Completed documents cannot be deleted.
@@ -670,7 +736,7 @@ const response = await fetch('https://app.documenso.com/api/v2/envelope/delete',
const { success } = await response.json(); const { success } = await response.json();
```` ```
</Tab> </Tab>
</Tabs> </Tabs>
@@ -680,7 +746,7 @@ const { success } = await response.json();
{ {
"success": true "success": true
} }
```` ```
--- ---
@@ -694,9 +760,11 @@ POST /envelope/get-many
### Request Body ### Request Body
| Field | Type | Required | Description | | Field | Type | Required | Description |
| ------------- | ----- | -------- | --------------------- | | ---------- | ------ | -------- | ---------------------------------------------------------------------------- |
| `envelopeIds` | array | Yes | Array of document IDs | | `ids` | object | Yes | ID selector containing `type` and `ids` |
| `ids.type` | string | Yes | `envelopeId`, `documentId`, or `templateId` |
| `ids.ids` | array | Yes | 1-20 IDs: strings for `envelopeId`; numbers for `documentId` or `templateId` |
### Code Examples ### Code Examples
@@ -707,12 +775,17 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/get-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \ -H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"envelopeIds": ["envelope_abc123", "envelope_def456", "envelope_ghi789"] "ids": {
"type": "envelopeId",
"ids": ["envelope_abc123", "envelope_def456", "envelope_ghi789"]
}
}' }'
``` ```
</Tab> </Tab>
<Tab value="TypeScript"> <Tab value="TypeScript">
```typescript ```typescript
const requestedIds = ['envelope_abc123', 'envelope_def456', 'envelope_ghi789'];
const response = await fetch('https://app.documenso.com/api/v2/envelope/get-many', { const response = await fetch('https://app.documenso.com/api/v2/envelope/get-many', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -720,16 +793,36 @@ const response = await fetch('https://app.documenso.com/api/v2/envelope/get-many
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
envelopeIds: ['envelope_abc123', 'envelope_def456', 'envelope_ghi789'], ids: {
type: 'envelopeId',
ids: requestedIds,
},
}), }),
}); });
const documents = await response.json(); const { data } = await response.json();
```` ```
</Tab> </Tab>
</Tabs> </Tabs>
### Response
```json
{
"data": [
{
"id": "envelope_abc123",
"type": "DOCUMENT",
"status": "PENDING",
"title": "Service Agreement"
}
]
}
```
The endpoint silently omits envelopes you cannot access instead of returning `404`. Compare `data.length` with `requestedIds.length` to detect omissions.
--- ---
## Document Statuses ## Document Statuses
@@ -740,6 +833,7 @@ const documents = await response.json();
| `PENDING` | Document has been sent. Waiting for recipients to sign. | | `PENDING` | Document has been sent. Waiting for recipients to sign. |
| `COMPLETED` | All recipients have signed. Document is sealed. | | `COMPLETED` | All recipients have signed. Document is sealed. |
| `REJECTED` | A recipient rejected the document. | | `REJECTED` | A recipient rejected the document. |
| `CANCELLED` | The document was cancelled by its owner or a team member with `MANAGER` or higher permissions. |
### Status Transitions ### Status Transitions
@@ -747,11 +841,13 @@ const documents = await response.json();
flowchart LR flowchart LR
DRAFT --> PENDING --> COMPLETED DRAFT --> PENDING --> COMPLETED
PENDING --> REJECTED PENDING --> REJECTED
PENDING --> CANCELLED
``` ```
- **DRAFT to PENDING**: Call the distribute endpoint - **DRAFT to PENDING**: Call the distribute endpoint
- **PENDING to COMPLETED**: All recipients complete their signing - **PENDING to COMPLETED**: All recipients complete their signing
- **PENDING to REJECTED**: A recipient rejects the document - **PENDING to REJECTED**: A recipient rejects the document
- **PENDING to CANCELLED**: The document owner or a team member with `MANAGER` or higher permissions cancels the document
<Callout type="warn"> <Callout type="warn">
You cannot modify recipients or fields after a document moves to `PENDING` status. You cannot modify recipients or fields after a document moves to `PENDING` status.
@@ -773,8 +869,8 @@ flowchart LR
| Parameter | Values | Description | | Parameter | Values | Description |
| ---------- | ------------------------------------------- | ------------------------- | | ---------- | ------------------------------------------- | ------------------------- |
| `type` | `DOCUMENT`, `TEMPLATE` | Filter by envelope type | | `type` | `DOCUMENT`, `TEMPLATE` | Filter by envelope type |
| `status` | `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED` | Filter by status | | `status` | `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED`, `CANCELLED` | Filter by status |
| `source` | `DOCUMENT`, `TEMPLATE`, `API` | Filter by creation source | | `source` | `DOCUMENT`, `TEMPLATE`, `TEMPLATE_DIRECT_LINK` | Filter by creation source |
| `folderId` | string | Filter by folder | | `folderId` | string | Filter by folder |
### Sorting ### Sorting
@@ -800,10 +896,10 @@ async function getAllPendingDocuments() {
}, },
); );
const { data, pagination } = await response.json(); const { data, currentPage, totalPages } = await response.json();
documents.push(...data); documents.push(...data);
hasMore = page < pagination.totalPages; hasMore = currentPage < totalPages;
page++; page++;
} }
+159 -87
View File
@@ -19,13 +19,13 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
| `secondaryId` | string | Secondary identifier for audit logs | | `secondaryId` | string | Secondary identifier for audit logs |
| `type` | string | Field type (see [Field Types](#field-types)) | | `type` | string | Field type (see [Field Types](#field-types)) |
| `recipientId` | number | ID of the recipient assigned to this field | | `recipientId` | number | ID of the recipient assigned to this field |
| `envelopeId` | number | ID of the parent envelope | | `envelopeId` | string | ID of the parent envelope |
| `envelopeItemId` | string | ID of the PDF item the field is placed on | | `envelopeItemId` | string | ID of the PDF item the field is placed on |
| `page` | number | Page number (1-indexed) | | `page` | number | Page number (1-indexed) |
| `positionX` | number | X coordinate as percentage (0-100) | | `positionX` | string | X coordinate as percentage (0-100), a decimal serialized as a string |
| `positionY` | number | Y coordinate as percentage (0-100) | | `positionY` | string | Y coordinate as percentage (0-100), a decimal serialized as a string |
| `width` | number | Width as percentage of page (0-100) | | `width` | string | Width as percentage of page (0-100), a decimal serialized as a string |
| `height` | number | Height as percentage of page (0-100) | | `height` | string | Height as percentage of page (0-100), a decimal serialized as a string |
| `customText` | string | Value entered by the recipient | | `customText` | string | Value entered by the recipient |
| `inserted` | boolean | Whether the field has been completed | | `inserted` | boolean | Whether the field has been completed |
| `fieldMeta` | object \| null | Type-specific configuration options | | `fieldMeta` | object \| null | Type-specific configuration options |
@@ -38,18 +38,19 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
"secondaryId": "field_abc123", "secondaryId": "field_abc123",
"type": "SIGNATURE", "type": "SIGNATURE",
"recipientId": 123, "recipientId": 123,
"envelopeId": 789, "envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_xyz", "envelopeItemId": "envelope_item_abcdefhiklmnorst",
"page": 1, "page": 1,
"positionX": 10, "positionX": "10",
"positionY": 80, "positionY": "80",
"width": 30, "width": "30",
"height": 5, "height": "5",
"customText": "", "customText": "",
"inserted": false, "inserted": false,
"fieldMeta": { "fieldMeta": {
"type": "signature", "type": "signature",
"required": true "required": true,
"overflow": "auto"
} }
} }
``` ```
@@ -134,10 +135,10 @@ POST /envelope/field/create-many
### Request Body ### Request Body
| Field | Type | Required | Description | | Field | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------- | | ------------ | ------ | -------- | ------------------------------- |
| `documentId`| number | Yes | The document ID | | `envelopeId` | string | Yes | The envelope ID |
| `fields` | array | Yes | Array of field configurations | | `data` | array | Yes | Array of field configurations |
### Code Examples ### Code Examples
@@ -148,32 +149,32 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/field/create-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \ -H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"documentId": 123, "envelopeId": "envelope_abcdefhiklmnorst",
"fields": [ "data": [
{ {
"type": "SIGNATURE", "type": "SIGNATURE",
"recipientId": 456, "recipientId": 456,
"pageNumber": 1, "page": 1,
"pageX": 10, "positionX": 10,
"pageY": 80, "positionY": 80,
"width": 30, "width": 30,
"height": 5 "height": 5
}, },
{ {
"type": "DATE", "type": "DATE",
"recipientId": 456, "recipientId": 456,
"pageNumber": 1, "page": 1,
"pageX": 50, "positionX": 50,
"pageY": 80, "positionY": 80,
"width": 20, "width": 20,
"height": 3 "height": 3
}, },
{ {
"type": "TEXT", "type": "TEXT",
"recipientId": 456, "recipientId": 456,
"pageNumber": 1, "page": 1,
"pageX": 10, "positionX": 10,
"pageY": 70, "positionY": 70,
"width": 40, "width": 40,
"height": 4, "height": 4,
"fieldMeta": { "fieldMeta": {
@@ -199,32 +200,32 @@ const response = await fetch(
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
documentId: 123, envelopeId: 'envelope_abcdefhiklmnorst',
fields: [ data: [
{ {
type: 'SIGNATURE', type: 'SIGNATURE',
recipientId: 456, recipientId: 456,
pageNumber: 1, page: 1,
pageX: 10, positionX: 10,
pageY: 80, positionY: 80,
width: 30, width: 30,
height: 5, height: 5,
}, },
{ {
type: 'DATE', type: 'DATE',
recipientId: 456, recipientId: 456,
pageNumber: 1, page: 1,
pageX: 50, positionX: 50,
pageY: 80, positionY: 80,
width: 20, width: 20,
height: 3, height: 3,
}, },
{ {
type: 'TEXT', type: 'TEXT',
recipientId: 456, recipientId: 456,
pageNumber: 1, page: 1,
pageX: 10, positionX: 10,
pageY: 70, positionY: 70,
width: 40, width: 40,
height: 4, height: 4,
fieldMeta: { fieldMeta: {
@@ -239,8 +240,8 @@ const response = await fetch(
} }
); );
const { fields } = await response.json(); const { data } = await response.json();
console.log(`Created ${fields.length} fields`); console.log(`Created ${data.length} fields`);
```` ````
</Tab> </Tab>
@@ -250,36 +251,68 @@ console.log(`Created ${fields.length} fields`);
```json ```json
{ {
"fields": [ "data": [
{ {
"id": 101, "id": 101,
"secondaryId": "field_abc123",
"envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "SIGNATURE", "type": "SIGNATURE",
"recipientId": 456, "recipientId": 456,
"page": 1, "page": 1,
"positionX": 10, "positionX": "10",
"positionY": 80, "positionY": "80",
"width": 30, "width": "30",
"height": 5 "height": "5",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "signature",
"fontSize": 18,
"overflow": "auto"
}
}, },
{ {
"id": 102, "id": 102,
"secondaryId": "field_def456",
"envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "DATE", "type": "DATE",
"recipientId": 456, "recipientId": 456,
"page": 1, "page": 1,
"positionX": 50, "positionX": "50",
"positionY": 80, "positionY": "80",
"width": 20, "width": "20",
"height": 3 "height": "3",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "date",
"fontSize": 12,
"textAlign": "left",
"overflow": "auto"
}
}, },
{ {
"id": 103, "id": 103,
"secondaryId": "field_ghi789",
"envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "TEXT", "type": "TEXT",
"recipientId": 456, "recipientId": 456,
"page": 1, "page": 1,
"positionX": 10, "positionX": "10",
"positionY": 70, "positionY": "70",
"width": 40, "width": "40",
"height": 4 "height": "4",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "text",
"label": "Job Title",
"placeholder": "Enter your job title",
"required": true
}
} }
] ]
} }
@@ -299,8 +332,8 @@ POST /envelope/field/update-many
| Field | Type | Required | Description | | Field | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------- | | ------------ | ------ | -------- | ----------------------------- |
| `documentId` | number | Yes | The document ID | | `envelopeId` | string | Yes | The envelope ID |
| `fields` | array | Yes | Array of field update objects | | `data` | array | Yes | Array of field update objects |
### Code Examples ### Code Examples
@@ -311,17 +344,17 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/field/update-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \ -H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"documentId": 123, "envelopeId": "envelope_abcdefhiklmnorst",
"fields": [ "data": [
{ {
"id": 101, "id": 101,
"type": "SIGNATURE", "type": "SIGNATURE",
"pageY": 85 "positionY": 85
}, },
{ {
"id": 102, "id": 102,
"type": "DATE", "type": "DATE",
"pageY": 85 "positionY": 85
} }
] ]
}' }'
@@ -338,16 +371,16 @@ const response = await fetch(
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
documentId: 123, envelopeId: 'envelope_abcdefhiklmnorst',
fields: [ data: [
{ id: 101, type: 'SIGNATURE', pageY: 85 }, { id: 101, type: 'SIGNATURE', positionY: 85 },
{ id: 102, type: 'DATE', pageY: 85 }, { id: 102, type: 'DATE', positionY: 85 },
], ],
}), }),
} }
); );
const { fields } = await response.json(); const { data } = await response.json();
```` ````
</Tab> </Tab>
@@ -357,9 +390,48 @@ const { fields } = await response.json();
```json ```json
{ {
"fields": [ "data": [
{ "id": 101, "type": "SIGNATURE", "positionY": 85 }, {
{ "id": 102, "type": "DATE", "positionY": 85 } "id": 101,
"secondaryId": "field_abc123",
"envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "SIGNATURE",
"recipientId": 456,
"page": 1,
"positionX": "10",
"positionY": "85",
"width": "30",
"height": "5",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "signature",
"fontSize": 18,
"overflow": "auto"
}
},
{
"id": 102,
"secondaryId": "field_def456",
"envelopeId": "envelope_abcdefhiklmnorst",
"envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "DATE",
"recipientId": 456,
"page": 1,
"positionX": "50",
"positionY": "85",
"width": "20",
"height": "3",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "date",
"fontSize": 12,
"textAlign": "left",
"overflow": "auto"
}
}
] ]
} }
```` ````
@@ -443,8 +515,8 @@ Fields use percentage-based coordinates relative to the PDF page dimensions.
(0,0) ─────────────────────────── (100,0) (0,0) ─────────────────────────── (100,0)
│ │ │ │
│ ┌─────────┐ │ │ ┌─────────┐ │
│ │ Field │ (pageX: 10, │ │ Field │ (positionX: 10, │
│ │ │ pageY: 20, │ │ │ positionY: 20, │
│ └─────────┘ width: 30, │ │ └─────────┘ width: 30, │
│ height: 5) │ │ height: 5) │
│ │ │ │
@@ -457,9 +529,9 @@ Fields use percentage-based coordinates relative to the PDF page dimensions.
const field = { const field = {
type: 'SIGNATURE', type: 'SIGNATURE',
recipientId: 123, recipientId: 123,
pageNumber: 1, page: 1,
pageX: 60, // 60% from left positionX: 60, // 60% from left
pageY: 85, // 85% from top (near bottom) positionY: 85, // 85% from top (near bottom)
width: 30, // 30% of page width width: 30, // 30% of page width
height: 8, // 8% of page height height: 8, // 8% of page height
}; };
@@ -643,15 +715,15 @@ All field types support these base options:
Create a document with a signature block containing multiple field types: Create a document with a signature block containing multiple field types:
```typescript ```typescript
async function addSignatureBlock(documentId: number, recipientId: number) { async function addSignatureBlock(envelopeId: string, recipientId: number) {
const fields = [ const data = [
// Signature // Signature
{ {
type: 'SIGNATURE', type: 'SIGNATURE',
recipientId, recipientId,
pageNumber: 1, page: 1,
pageX: 10, positionX: 10,
pageY: 80, positionY: 80,
width: 30, width: 30,
height: 8, height: 8,
fieldMeta: { fieldMeta: {
@@ -663,9 +735,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{ {
type: 'NAME', type: 'NAME',
recipientId, recipientId,
pageNumber: 1, page: 1,
pageX: 10, positionX: 10,
pageY: 90, positionY: 90,
width: 30, width: 30,
height: 4, height: 4,
fieldMeta: { fieldMeta: {
@@ -677,9 +749,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{ {
type: 'DATE', type: 'DATE',
recipientId, recipientId,
pageNumber: 1, page: 1,
pageX: 50, positionX: 50,
pageY: 80, positionY: 80,
width: 20, width: 20,
height: 4, height: 4,
fieldMeta: { fieldMeta: {
@@ -691,9 +763,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{ {
type: 'TEXT', type: 'TEXT',
recipientId, recipientId,
pageNumber: 1, page: 1,
pageX: 50, positionX: 50,
pageY: 90, positionY: 90,
width: 30, width: 30,
height: 4, height: 4,
fieldMeta: { fieldMeta: {
@@ -710,7 +782,7 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
Authorization: 'api_xxxxxxxxxxxxxxxx', Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ documentId, fields }), body: JSON.stringify({ envelopeId, data }),
}); });
return response.json(); return response.json();
@@ -119,7 +119,7 @@ Full reference in the [V2 OpenAPI reference](https://openapi.documenso.com).
| ------------------------------------------------- | ----------------------------------------------------- | | ------------------------------------------------- | ----------------------------------------------------- |
| `GET /api/v2/document` | `GET /api/v2/envelope` | | `GET /api/v2/document` | `GET /api/v2/envelope` |
| `GET /api/v2/document/{documentId}` | `GET /api/v2/envelope/{envelopeId}` | | `GET /api/v2/document/{documentId}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v2/document/get-many` | `POST /api/v2/envelope/get-many` | | `POST /api/v2/document/get-many` | `POST /api/v2/envelope/get-many` (body changes from `documentIds: number[]` to `ids: { type: "documentId"; ids: number[] }`) |
| `POST /api/v2/document/create` | `POST /api/v2/envelope/create` | | `POST /api/v2/document/create` | `POST /api/v2/envelope/create` |
| `POST /api/v2/document/create/beta` | `POST /api/v2/envelope/create` | | `POST /api/v2/document/create/beta` | `POST /api/v2/envelope/create` |
| `POST /api/v2/document/update` | `POST /api/v2/envelope/update` | | `POST /api/v2/document/update` | `POST /api/v2/envelope/update` |
@@ -140,7 +140,7 @@ Full reference in the [V2 OpenAPI reference](https://openapi.documenso.com).
| ------------------------------------- | ------------------------------------------------ | | ------------------------------------- | ------------------------------------------------ |
| `GET /api/v2/template` | `GET /api/v2/envelope` (with `type=TEMPLATE`) | | `GET /api/v2/template` | `GET /api/v2/envelope` (with `type=TEMPLATE`) |
| `GET /api/v2/template/{templateId}` | `GET /api/v2/envelope/{envelopeId}` | | `GET /api/v2/template/{templateId}` | `GET /api/v2/envelope/{envelopeId}` |
| `POST /api/v2/template/get-many` | `POST /api/v2/envelope/get-many` | | `POST /api/v2/template/get-many` | `POST /api/v2/envelope/get-many` (body changes from `templateIds: number[]` to `ids: { type: "templateId"; ids: number[] }`) |
| `POST /api/v2/template/create` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) | | `POST /api/v2/template/create` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) |
| `POST /api/v2/template/create/beta` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) | | `POST /api/v2/template/create/beta` | `POST /api/v2/envelope/create` (`type=TEMPLATE`) |
| `POST /api/v2/template/update` | `POST /api/v2/envelope/update` | | `POST /api/v2/template/update` | `POST /api/v2/envelope/update` |
@@ -11,6 +11,12 @@ Documenso enforces rate limits on all API endpoints to ensure service stability.
## HTTP Rate Limits ## HTTP Rate Limits
The rate limit applies to:
- `/api/v1/*`
- `/api/v2/*`
- `/api/v2-beta/*`
**Limit:** 1000 requests per minute per IP address **Limit:** 1000 requests per minute per IP address
**Response:** 429 Too Many Requests **Response:** 429 Too Many Requests
@@ -19,7 +25,7 @@ Documenso enforces rate limits on all API endpoints to ensure service stability.
this value, in which case you can be rate-limited before reaching the global limit. this value, in which case you can be rate-limited before reaching the global limit.
</Callout> </Callout>
### Rate Limit Response ### Global per-IP 429 Response
```json ```json
{ {
@@ -27,10 +33,22 @@ Documenso enforces rate limits on all API endpoints to ensure service stability.
} }
``` ```
<Callout type="warn"> ### Rate Limit Headers
No rate limit headers are currently provided. When you receive a 429 response, wait at least 60
seconds before retrying. Responses from `/api/v1/*`, `/api/v2/*`, and `/api/v2-beta/*` include these headers. The only
</Callout> exception is CORS preflight (`OPTIONS`) requests, which are answered before the rate limiter runs
and carry no rate limit headers:
| Header | Description |
| ----------------------- | ---------------------------------------------------------------------- |
| `X-RateLimit-Limit` | Maximum requests allowed in the current global window |
| `X-RateLimit-Remaining` | Requests remaining in the current global window |
| `X-RateLimit-Reset` | End of the current global window, as a Unix epoch timestamp in seconds |
A 429 response from a windowed limiter also includes `Retry-After`, in seconds, with a minimum
value of `1`. The global API limit uses fixed, epoch-aligned one-minute buckets, so the actual wait
until the next window is between 1 and 60 seconds. Honor `Retry-After` exactly instead of sleeping
for a fixed 60 seconds. See the [Retry-After handling example](/docs/developers/examples/common-workflows#error-handling-patterns).
## Resource Limits ## Resource Limits
@@ -44,24 +62,55 @@ Beyond HTTP rate limits, your account has usage limits based on your subscriptio
| Total Recipients | 10 | Unlimited | Unlimited | Unlimited | | Total Recipients | 10 | Unlimited | Unlimited | Unlimited |
| Direct Templates | 3 | Unlimited | Unlimited | Unlimited | | Direct Templates | 3 | Unlimited | Unlimited | Unlimited |
### Error Response ### Organisation Limit 429 Responses
When you exceed a resource limit: Organisation windowed limits and organisation monthly quotas produce 429 responses whose body
shape depends on the API version, and neither matches the global per-IP limiter's
`{ "error": "..." }` body.
On `/api/v1/*`, the body contains only a message:
```json ```json
{ {
"error": "You have reached your document limit for this month. Please upgrade your plan.", "message": "Too many requests, please try again later. Contact support if you require higher limits."
"code": "LIMIT_EXCEEDED",
"statusCode": 400
} }
``` ```
On `/api/v2/*` and `/api/v2-beta/*`, the body is a structured error object:
```json
{
"message": "Too many requests, please try again later. Contact support if you require higher limits.",
"code": "TOO_MANY_REQUESTS",
"data": {
"code": "TOO_MANY_REQUESTS",
"httpStatus": 429,
"appError": {
"code": "TOO_MANY_REQUESTS",
"message": "Too many requests, please try again later. Contact support if you require higher limits."
}
}
}
```
Organisation windowed limit responses include the `X-RateLimit-*` headers and `Retry-After` for
their own window. Monthly quota responses carry no quota-specific rate limit headers or
`Retry-After` because the quota is not a time window; rely on the status code and message instead.
## Error Codes ## Error Codes
| Code | Status | Description | | Code | Status | Description |
| ------------------- | ------ | ----------------------------- | | ------------------- | ------ | ------------------------------------------------------------------ |
| `TOO_MANY_REQUESTS` | 429 | HTTP rate limit exceeded | | `TOO_MANY_REQUESTS` | 429 | Global per-IP, organisation windowed, or monthly quota exceeded |
| `LIMIT_EXCEEDED` | 400 | Resource usage limit exceeded | | `LIMIT_EXCEEDED` | 400 | Resource usage limit exceeded |
There are three sources of `TOO_MANY_REQUESTS` responses:
1. The global per-IP limit, returning the `{ "error": "..." }` body shown above.
2. Organisation windowed rate limits for the `api`, `document`, and `email` counters.
3. Organisation monthly quotas for the same three counters. Every authenticated API request
consumes the `api` counter, so any endpoint can return this 429 once the monthly API quota is
exhausted — not just envelope-related ones.
--- ---
@@ -1000,9 +1000,12 @@ async function fetchWithRetry(
// Retry on rate limit // Retry on rate limit
if (response.status === 429) { if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After'); const retryAfter = response.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : baseDelayMs * Math.pow(2, attempt); // Honor Retry-After exactly; the cap only applies to the exponential fallback.
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
console.log(`Rate limited, waiting ${delay}ms...`); console.log(`Rate limited, waiting ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, Math.min(delay, maxDelayMs))); await new Promise((resolve) => setTimeout(resolve, delay));
continue; continue;
} }
@@ -78,12 +78,10 @@ A successful response returns a list of your documents (envelopes):
"createdAt": "2025-01-15T10:30:00.000Z" "createdAt": "2025-01-15T10:30:00.000Z"
} }
], ],
"pagination": { "count": 1,
"page": 1, "currentPage": 1,
"perPage": 10, "perPage": 10,
"totalPages": 1, "totalPages": 1
"totalItems": 1
}
} }
```` ````
@@ -228,9 +226,12 @@ After creating a document, it's in `DRAFT` status. To send it to recipients, use
<Tabs items={['curl', 'JavaScript']}> <Tabs items={['curl', 'JavaScript']}>
<Tab value="curl"> <Tab value="curl">
```bash ```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/envelope_abc123/distribute" \ curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
-H "Authorization: YOUR_API_TOKEN" \ -H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" -H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123"
}'
```` ````
</Tab> </Tab>
@@ -238,16 +239,14 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/envelope_abc123/distribu
```javascript ```javascript
const envelopeId = 'envelope_abc123'; const envelopeId = 'envelope_abc123';
const response = await fetch( const response = await fetch('https://app.documenso.com/api/v2/envelope/distribute', {
`https://app.documenso.com/api/v2/envelope/${envelopeId}/distribute`, method: 'POST',
{ headers: {
method: 'POST', Authorization: 'YOUR_API_TOKEN',
headers: { 'Content-Type': 'application/json',
Authorization: 'YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
}, },
); body: JSON.stringify({ envelopeId }),
});
const data = await response.json(); const data = await response.json();
console.log('Document sent:', data); console.log('Document sent:', data);
@@ -337,16 +336,14 @@ async function createAndSendDocument(pdfPath, recipientEmail, recipientName) {
console.log('Created envelope:', envelope.id); console.log('Created envelope:', envelope.id);
// Step 2: Send the document for signing // Step 2: Send the document for signing
const distributeResponse = await fetch( const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
`${BASE_URL}/envelope/${envelope.id}/distribute`, method: 'POST',
{ headers: {
method: 'POST', 'Authorization': API_TOKEN,
headers: { 'Content-Type': 'application/json',
'Authorization': API_TOKEN, },
'Content-Type': 'application/json', body: JSON.stringify({ envelopeId: envelope.id }),
}, });
}
);
if (!distributeResponse.ok) { if (!distributeResponse.ok) {
const error = await distributeResponse.json(); const error = await distributeResponse.json();
@@ -422,9 +419,12 @@ echo "Created envelope: ${ENVELOPE_ID}"
# Step 2: Send the document for signing # Step 2: Send the document for signing
echo "Sending document..." echo "Sending document..."
curl -s -X POST "${BASE_URL}/envelope/${ENVELOPE_ID}/distribute" \ curl -s -X POST "${BASE_URL}/envelope/distribute" \
-H "Authorization: ${API_TOKEN}" \ -H "Authorization: ${API_TOKEN}" \
-H "Content-Type: application/json" -H "Content-Type: application/json" \
-d "{
\"envelopeId\": \"${ENVELOPE_ID}\"
}"
echo "Document sent for signing!" echo "Document sent for signing!"
@@ -441,7 +441,7 @@ The API returns standard HTTP status codes and JSON error responses:
| `400` | Bad request - check your request payload | | `400` | Bad request - check your request payload |
| `401` | Unauthorized - invalid or missing API token | | `401` | Unauthorized - invalid or missing API token |
| `404` | Not found - resource doesn't exist | | `404` | Not found - resource doesn't exist |
| `429` | Rate limited - wait 60 seconds and retry | | `429` | Rate limited - wait for the duration in the `Retry-After` header |
| `500` | Server error - retry or contact support | | `500` | Server error - retry or contact support |
### Error Response Format ### Error Response Format
@@ -485,7 +485,7 @@ The API returns standard HTTP status codes and JSON error responses:
### Handling Rate Limits ### Handling Rate Limits
The API allows 1000 requests per minute per IP address. Your organisation may have its own lower rate limits. When rate limited, wait at least 60 seconds before retrying: The API allows 1000 requests per minute per IP address. Your organisation may have its own lower rate limits. Every response includes `X-RateLimit-Remaining` and `X-RateLimit-Reset` (an epoch timestamp in seconds). When you receive a `429` response, read the `Retry-After` header and wait for that many seconds before retrying. See [Error Handling Patterns](/docs/developers/examples/common-workflows#error-handling-patterns) for a more complete retry strategy.
```javascript ```javascript
async function fetchWithRetry(url, options, maxRetries = 3) { async function fetchWithRetry(url, options, maxRetries = 3) {
@@ -493,8 +493,9 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
const response = await fetch(url, options); const response = await fetch(url, options);
if (response.status === 429) { if (response.status === 429) {
console.log('Rate limited, waiting 60 seconds...'); const retryAfterSeconds = Number.parseInt(response.headers.get('Retry-After') ?? '1', 10);
await new Promise((resolve) => setTimeout(resolve, 60000)); console.log(`Rate limited, waiting ${retryAfterSeconds} seconds...`);
await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
continue; continue;
} }
@@ -33,13 +33,14 @@ All webhook events share a common structure:
| Field | Type | Description | | Field | Type | Description |
| ---------------- | --------- | ------------------------------------------------------ | | ---------------- | --------- | ------------------------------------------------------ |
| `id` | number | Document or template ID | | `id` | number | Legacy numeric v1 document or template ID |
| `envelopeId` | string | Canonical v2 identifier (`envelope_` + 16 characters) |
| `externalId` | string? | External identifier for integration | | `externalId` | string? | External identifier for integration |
| `userId` | number | Owner's user ID | | `userId` | number | Owner's user ID |
| `authOptions` | object? | Document-level authentication options | | `authOptions` | object? | Document-level authentication options |
| `formValues` | object? | PDF form values associated with the document | | `formValues` | object? | PDF form values associated with the document |
| `title` | string | Document or template title | | `title` | string | Document or template title |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED` | | `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED`, `CANCELLED` |
| `visibility` | string | Document visibility setting | | `visibility` | string | Document visibility setting |
| `createdAt` | datetime | Document creation timestamp | | `createdAt` | datetime | Document creation timestamp |
| `updatedAt` | datetime | Last modification timestamp | | `updatedAt` | datetime | Last modification timestamp |
@@ -47,8 +48,8 @@ All webhook events share a common structure:
| `deletedAt` | datetime? | Deletion timestamp | | `deletedAt` | datetime? | Deletion timestamp |
| `teamId` | number? | Team ID if document belongs to a team | | `teamId` | number? | Team ID if document belongs to a team |
| `templateId` | number? | Template ID if created from a template | | `templateId` | number? | Template ID if created from a template |
| `source` | string | Source: `DOCUMENT` or `TEMPLATE` | | `source` | string | Source: `DOCUMENT`, `TEMPLATE`, or `TEMPLATE_DIRECT_LINK` |
| `documentMeta` | object | Document metadata (subject, message, signing options) | | `documentMeta` | object? | Nullable document metadata (subject, message, signing options) |
| `recipients` | array | List of recipient objects | | `recipients` | array | List of recipient objects |
| `Recipient` | array | List of recipient objects (legacy, same as recipients) | | `Recipient` | array | List of recipient objects (legacy, same as recipients) |
@@ -60,7 +61,6 @@ All webhook events share a common structure:
| `subject` | string? | Email subject line | | `subject` | string? | Email subject line |
| `message` | string? | Email message body | | `message` | string? | Email message body |
| `timezone` | string | Timezone for date display | | `timezone` | string | Timezone for date display |
| `password` | string? | Document access password (if set) |
| `dateFormat` | string | Date format string | | `dateFormat` | string | Date format string |
| `redirectUrl` | string? | URL to redirect after signing | | `redirectUrl` | string? | URL to redirect after signing |
| `signingOrder` | string | `PARALLEL` or `SEQUENTIAL` | | `signingOrder` | string | `PARALLEL` or `SEQUENTIAL` |
@@ -77,8 +77,9 @@ All webhook events share a common structure:
| Field | Type | Description | | Field | Type | Description |
| ---------------------- | --------- | ------------------------------------------ | | ---------------------- | --------- | ------------------------------------------ |
| `id` | number | Recipient ID | | `id` | number | Recipient ID |
| `documentId` | number? | Parent document ID | | `envelopeId` | string | Canonical parent envelope ID |
| `templateId` | number? | Template ID if created from a template | | `documentId` | number? | Legacy parent document ID; null for templates |
| `templateId` | number? | Legacy parent template ID; null for documents |
| `email` | string | Recipient email address | | `email` | string | Recipient email address |
| `name` | string | Recipient name | | `name` | string | Recipient name |
| `token` | string | Unique signing token | | `token` | string | Unique signing token |
@@ -94,6 +95,8 @@ All webhook events share a common structure:
| `sendStatus` | string | `NOT_SENT` or `SENT` | | `sendStatus` | string | `NOT_SENT` or `SENT` |
| `rejectionReason` | string? | Reason if recipient rejected | | `rejectionReason` | string? | Reason if recipient rejected |
Use `recipient.envelopeId` as the reliable parent link. The legacy `documentId` and `templateId` fields depend on the parent envelope type, so one of them is always null.
--- ---
## Document Lifecycle Events ## Document Lifecycle Events
@@ -111,6 +114,7 @@ Triggered when a new document is created.
"event": "DOCUMENT_CREATED", "event": "DOCUMENT_CREATED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null, "externalId": null,
"userId": 1, "userId": 1,
"authOptions": null, "authOptions": null,
@@ -129,9 +133,8 @@ Triggered when a new document is created.
"id": "doc_meta_123", "id": "doc_meta_123",
"subject": "Please sign this document", "subject": "Please sign this document",
"message": "Hello, please review and sign this document.", "message": "Hello, please review and sign this document.",
"timezone": "UTC", "timezone": "Etc/UTC",
"password": null, "dateFormat": "yyyy-MM-dd hh:mm a",
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null, "redirectUrl": null,
"signingOrder": "PARALLEL", "signingOrder": "PARALLEL",
"allowDictateNextSigner": false, "allowDictateNextSigner": false,
@@ -145,6 +148,7 @@ Triggered when a new document is created.
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -166,6 +170,7 @@ Triggered when a new document is created.
"Recipient": [ "Recipient": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -203,6 +208,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"event": "DOCUMENT_SENT", "event": "DOCUMENT_SENT",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null, "externalId": null,
"userId": 1, "userId": 1,
"authOptions": null, "authOptions": null,
@@ -221,9 +227,8 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"id": "doc_meta_123", "id": "doc_meta_123",
"subject": "Please sign this document", "subject": "Please sign this document",
"message": "Hello, please review and sign this document.", "message": "Hello, please review and sign this document.",
"timezone": "UTC", "timezone": "Etc/UTC",
"password": null, "dateFormat": "yyyy-MM-dd hh:mm a",
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null, "redirectUrl": null,
"signingOrder": "PARALLEL", "signingOrder": "PARALLEL",
"allowDictateNextSigner": false, "allowDictateNextSigner": false,
@@ -237,6 +242,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -258,6 +264,7 @@ The document status changes to `PENDING` and recipients have `sendStatus: "SENT"
"Recipient": [ "Recipient": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -295,12 +302,14 @@ The recipient's `readStatus` changes to `OPENED`.
"event": "DOCUMENT_OPENED", "event": "DOCUMENT_OPENED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING", "status": "PENDING",
"title": "contract.pdf", "title": "contract.pdf",
"source": "DOCUMENT", "source": "DOCUMENT",
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
@@ -328,6 +337,7 @@ The recipient's `signingStatus` changes to `SIGNED` and `signedAt` is populated.
"event": "DOCUMENT_SIGNED", "event": "DOCUMENT_SIGNED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "COMPLETED", "status": "COMPLETED",
"title": "contract.pdf", "title": "contract.pdf",
"source": "DOCUMENT", "source": "DOCUMENT",
@@ -335,6 +345,7 @@ The recipient's `signingStatus` changes to `SIGNED` and `signedAt` is populated.
"recipients": [ "recipients": [
{ {
"id": 51, "id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
@@ -361,12 +372,14 @@ Triggered when an individual recipient completes their required action (signing,
"event": "DOCUMENT_RECIPIENT_COMPLETED", "event": "DOCUMENT_RECIPIENT_COMPLETED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING", "status": "PENDING",
"title": "contract.pdf", "title": "contract.pdf",
"source": "DOCUMENT", "source": "DOCUMENT",
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
@@ -395,6 +408,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"event": "DOCUMENT_COMPLETED", "event": "DOCUMENT_COMPLETED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null, "externalId": null,
"userId": 1, "userId": 1,
"authOptions": null, "authOptions": null,
@@ -413,9 +427,8 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"id": "doc_meta_123", "id": "doc_meta_123",
"subject": "Please sign this document", "subject": "Please sign this document",
"message": "Hello, please review and sign this document.", "message": "Hello, please review and sign this document.",
"timezone": "UTC", "timezone": "Etc/UTC",
"password": null, "dateFormat": "yyyy-MM-dd hh:mm a",
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null, "redirectUrl": null,
"signingOrder": "PARALLEL", "signingOrder": "PARALLEL",
"allowDictateNextSigner": false, "allowDictateNextSigner": false,
@@ -429,6 +442,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"recipients": [ "recipients": [
{ {
"id": 50, "id": 50,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "reviewer@example.com", "email": "reviewer@example.com",
@@ -451,6 +465,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
}, },
{ {
"id": 51, "id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -475,6 +490,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
"Recipient": [ "Recipient": [
{ {
"id": 50, "id": 50,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "reviewer@example.com", "email": "reviewer@example.com",
@@ -497,6 +513,7 @@ The document status changes to `COMPLETED` and `completedAt` is set.
}, },
{ {
"id": 51, "id": 51,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10, "documentId": 10,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -537,12 +554,14 @@ The recipient's `signingStatus` changes to `REJECTED` and `rejectionReason` cont
"event": "DOCUMENT_REJECTED", "event": "DOCUMENT_REJECTED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING", "status": "PENDING",
"title": "contract.pdf", "title": "contract.pdf",
"source": "DOCUMENT", "source": "DOCUMENT",
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
@@ -561,7 +580,7 @@ The recipient's `signingStatus` changes to `REJECTED` and `rejectionReason` cont
### `document.cancelled` ### `document.cancelled`
Triggered when the document owner or a team member deletes a document. Draft and pending documents are hard-deleted, while completed documents are soft-deleted. Triggered when a pending document is explicitly cancelled with `POST /envelope/cancel`, or when a document owner or team member deletes a document. Deleting a draft or pending document hard-deletes it, while deleting a completed document soft-deletes it.
This event is **not** triggered when a recipient hides a document from their inbox. This event is **not** triggered when a recipient hides a document from their inbox.
@@ -572,6 +591,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"event": "DOCUMENT_CANCELLED", "event": "DOCUMENT_CANCELLED",
"payload": { "payload": {
"id": 7, "id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"externalId": null, "externalId": null,
"userId": 3, "userId": 3,
"authOptions": null, "authOptions": null,
@@ -591,7 +611,6 @@ This event is **not** triggered when a recipient hides a document from their inb
"subject": "", "subject": "",
"message": "", "message": "",
"timezone": "Etc/UTC", "timezone": "Etc/UTC",
"password": null,
"dateFormat": "yyyy-MM-dd hh:mm a", "dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "", "redirectUrl": "",
"signingOrder": "PARALLEL", "signingOrder": "PARALLEL",
@@ -606,6 +625,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"recipients": [ "recipients": [
{ {
"id": 7, "id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 7, "documentId": 7,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -627,6 +647,7 @@ This event is **not** triggered when a recipient hides a document from their inb
"Recipient": [ "Recipient": [
{ {
"id": 7, "id": 7,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 7, "documentId": 7,
"templateId": null, "templateId": null,
"email": "signer@example.com", "email": "signer@example.com",
@@ -651,6 +672,45 @@ This event is **not** triggered when a recipient hides a document from their inb
} }
``` ```
### `recipient.expired`
Triggered when a recipient's signing deadline passes on a pending document before they sign or reject it.
**Event name:** `RECIPIENT_EXPIRED`
The recipient's `expiresAt` contains the signing deadline, and `expirationNotifiedAt` is set when the expiration is processed.
```json
{
"event": "RECIPIENT_EXPIRED",
"payload": {
"id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING",
"title": "contract.pdf",
"source": "DOCUMENT",
"recipients": [
{
"id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
"expiresAt": "2024-04-22T11:51:00.000Z",
"expirationNotifiedAt": "2024-04-22T11:52:00.000Z",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:00.000Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.reminder.sent` ### `document.reminder.sent`
Triggered when a reminder email is sent to a recipient who has not yet completed their action. Triggered when a reminder email is sent to a recipient who has not yet completed their action.
@@ -662,12 +722,14 @@ Triggered when a reminder email is sent to a recipient who has not yet completed
"event": "DOCUMENT_REMINDER_SENT", "event": "DOCUMENT_REMINDER_SENT",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"status": "PENDING", "status": "PENDING",
"title": "contract.pdf", "title": "contract.pdf",
"source": "DOCUMENT", "source": "DOCUMENT",
"recipients": [ "recipients": [
{ {
"id": 52, "id": 52,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
@@ -686,7 +748,7 @@ Triggered when a reminder email is sent to a recipient who has not yet completed
## Template Events ## Template Events
Template events track changes to reusable document templates. Template payloads use the same structure as document payloads, with `source` set to `TEMPLATE` and `templateId` populated. Template events track changes to reusable document templates. Template payloads use the same structure as document payloads. For `TEMPLATE_CREATED`, `TEMPLATE_UPDATED`, and `TEMPLATE_DELETED` the template's own legacy numeric ID is in `id` and `templateId` is `null`. Only `TEMPLATE_USED` — whose payload describes the new document envelope created from the template — carries the originating template's legacy ID in `templateId`, with `source` set to `TEMPLATE`.
### `template.created` ### `template.created`
@@ -699,9 +761,10 @@ Triggered when a new template is created.
"event": "TEMPLATE_CREATED", "event": "TEMPLATE_CREATED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "My Template", "title": "My Template",
"status": "DRAFT", "status": "DRAFT",
"templateId": 10, "templateId": null,
"source": "TEMPLATE", "source": "TEMPLATE",
"recipients": [] "recipients": []
}, },
@@ -721,9 +784,10 @@ Triggered when a template's settings, recipients, or fields are modified.
"event": "TEMPLATE_UPDATED", "event": "TEMPLATE_UPDATED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "My Updated Template", "title": "My Updated Template",
"status": "DRAFT", "status": "DRAFT",
"templateId": 10, "templateId": null,
"source": "TEMPLATE", "source": "TEMPLATE",
"recipients": [] "recipients": []
}, },
@@ -743,9 +807,10 @@ Triggered when a template is deleted.
"event": "TEMPLATE_DELETED", "event": "TEMPLATE_DELETED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Deleted Template", "title": "Deleted Template",
"status": "DRAFT", "status": "DRAFT",
"templateId": 10, "templateId": null,
"source": "TEMPLATE", "source": "TEMPLATE",
"recipients": [] "recipients": []
}, },
@@ -765,6 +830,7 @@ Triggered when a document is created from a template. This event fires alongside
"event": "TEMPLATE_USED", "event": "TEMPLATE_USED",
"payload": { "payload": {
"id": 10, "id": 10,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Document from Template", "title": "Document from Template",
"status": "DRAFT", "status": "DRAFT",
"templateId": 10, "templateId": 10,
@@ -791,7 +857,8 @@ Triggered when a document is created from a template. This event fires alongside
| `DOCUMENT_RECIPIENT_COMPLETED` | Recipient completes their action | Recipient `signingStatus: "SIGNED"`, `signedAt` set | | `DOCUMENT_RECIPIENT_COMPLETED` | Recipient completes their action | Recipient `signingStatus: "SIGNED"`, `signedAt` set |
| `DOCUMENT_COMPLETED` | All recipients complete actions | `status: "COMPLETED"`, `completedAt` set | | `DOCUMENT_COMPLETED` | All recipients complete actions | `status: "COMPLETED"`, `completedAt` set |
| `DOCUMENT_REJECTED` | Recipient rejects document | Recipient `signingStatus: "REJECTED"`, `rejectionReason` set | | `DOCUMENT_REJECTED` | Recipient rejects document | Recipient `signingStatus: "REJECTED"`, `rejectionReason` set |
| `DOCUMENT_CANCELLED` | Owner or team member deletes document | Document cancelled or deleted | | `DOCUMENT_CANCELLED` | Pending document explicitly cancelled, or document deleted | `status: "CANCELLED"` after explicit cancellation; deletion may remove or soft-delete the document |
| `RECIPIENT_EXPIRED` | Recipient signing deadline passes | Recipient `expiresAt` passed, `expirationNotifiedAt` set |
| `DOCUMENT_REMINDER_SENT` | Reminder email sent to recipient | No status changes | | `DOCUMENT_REMINDER_SENT` | Reminder email sent to recipient | No status changes |
### Template Events ### Template Events
@@ -821,7 +888,7 @@ When processing webhook events:
**Process idempotently** — Webhooks may be retried, so handle duplicate events **Process idempotently** — Webhooks may be retried, so handle duplicate events
</Step> </Step>
<Step> <Step>
**Respond quickly** — Return a 200 status code within 30 seconds **Respond quickly** — Return a `2xx` status code within 10 seconds
</Step> </Step>
</Steps> </Steps>
@@ -42,12 +42,14 @@ Documenso supports webhook events for the full document lifecycle (created, sent
"event": "DOCUMENT_COMPLETED", "event": "DOCUMENT_COMPLETED",
"payload": { "payload": {
"id": 123, "id": 123,
"envelopeId": "envelope_abcdefhiklmnorst",
"title": "Contract", "title": "Contract",
"status": "COMPLETED", "status": "COMPLETED",
"completedAt": "2024-01-15T10:30:00.000Z", "completedAt": "2024-01-15T10:30:00.000Z",
"recipients": [ "recipients": [
{ {
"id": 1, "id": 1,
"envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com", "email": "signer@example.com",
"signingStatus": "SIGNED" "signingStatus": "SIGNED"
} }
@@ -58,6 +60,8 @@ Documenso supports webhook events for the full document lifecycle (created, sent
} }
``` ```
`payload.id` is the legacy numeric v1 ID. Use `payload.envelopeId` as the canonical v2 identifier. Each recipient repeats `envelopeId` as the reliable parent link because the legacy `documentId` and `templateId` fields depend on the parent envelope type, leaving one of them null.
--- ---
## See Also ## See Also
@@ -148,7 +148,7 @@ func main() {
</Tabs> </Tabs>
<Callout type="warn"> <Callout type="warn">
Always respond with a `200 OK` status within 30 seconds. Documenso will retry failed deliveries. Always respond with a `2xx` status within 10 seconds. Documenso will retry failed deliveries according to the configured background-job provider.
</Callout> </Callout>
## Configuring Webhooks in Documenso via the Dashboard ## Configuring Webhooks in Documenso via the Dashboard
@@ -184,7 +184,7 @@ Fill in the following fields:
| Field | Description | | Field | Description |
| ----- | ----------- | | ----- | ----------- |
| **Webhook URL** | The HTTPS endpoint that will receive webhook events | | **Webhook URL** | The HTTP or HTTPS endpoint that will receive webhook events |
| **Events** | Select which events should trigger this webhook | | **Events** | Select which events should trigger this webhook |
| **Secret** (optional) | A secret key used to sign the payload for verification | | **Secret** (optional) | A secret key used to sign the payload for verification |
</Step> </Step>
@@ -202,12 +202,21 @@ Your webhook endpoint must meet these requirements:
| Requirement | Details | | Requirement | Details |
| ----------- | ------- | | ----------- | ------- |
| **Protocol** | HTTPS required (HTTP not allowed in production) | | **Protocol** | HTTP and HTTPS are accepted; use HTTPS in production |
| **Response** | Must return `2xx` status code within 30 seconds | | **Response** | Must return a `2xx` status code within 10 seconds |
| **Method** | Must accept HTTP POST requests | | **Method** | Must accept HTTP POST requests |
| **Content-Type** | Must accept `application/json` payloads | | **Content-Type** | Must accept `application/json` payloads |
| **Availability** | Must be publicly accessible from the internet | | **Availability** | Must be publicly accessible from the internet |
<Callout type="warn">
Documenso performs a best-effort check that rejects webhook URLs which use or resolve to private
or loopback addresses. This is not a complete SSRF mitigation — it does not cover DNS rebinding
and fails open on DNS lookup errors or timeouts — so self-hosted deployments should still enforce
network-level egress rules. Self-hosters that need to deliver to a hostname resolving to a
private address can add that hostname to the comma-separated
`NEXT_PRIVATE_WEBHOOK_SSRF_BYPASS_HOSTS` environment variable.
</Callout>
<Callout type="info"> <Callout type="info">
For local development, use a tunneling service like [ngrok](https://ngrok.com) or [localtunnel](https://localtunnel.me) to expose your local server. For local development, use a tunneling service like [ngrok](https://ngrok.com) or [localtunnel](https://localtunnel.me) to expose your local server.
</Callout> </Callout>
@@ -225,7 +234,8 @@ When creating a webhook, you can subscribe to one or more events:
| `DOCUMENT_RECIPIENT_COMPLETED` | A recipient completes their required action | | `DOCUMENT_RECIPIENT_COMPLETED` | A recipient completes their required action |
| `DOCUMENT_COMPLETED` | All recipients have completed their actions | | `DOCUMENT_COMPLETED` | All recipients have completed their actions |
| `DOCUMENT_REJECTED` | A recipient rejects the document | | `DOCUMENT_REJECTED` | A recipient rejects the document |
| `DOCUMENT_CANCELLED` | The document owner deletes the document | | `DOCUMENT_CANCELLED` | A pending document is explicitly cancelled or a document owner deletes it |
| `RECIPIENT_EXPIRED` | A recipient's signing deadline passes before they sign or reject |
| `DOCUMENT_REMINDER_SENT` | A reminder email is sent to a recipient | | `DOCUMENT_REMINDER_SENT` | A reminder email is sent to a recipient |
| `TEMPLATE_CREATED` | A new template is created | | `TEMPLATE_CREATED` | A new template is created |
| `TEMPLATE_UPDATED` | A template is modified | | `TEMPLATE_UPDATED` | A template is modified |
@@ -318,17 +328,17 @@ Documenso will attempt to deliver the same payload again
## Retry Policy ## Retry Policy
When a webhook delivery fails (non-2xx response or timeout), Documenso automatically retries with exponential backoff: A delivery fails when the endpoint returns a non-`2xx` response, the 10-second timeout expires, or the request fails. Redirects are not followed, so `3xx` responses also fail. Network and SSRF-blocked requests are recorded with response code `0`.
| Attempt | Delay | For self-hosted deployments, retries are handled by the background-job provider selected with `NEXT_PRIVATE_JOBS_PROVIDER`:
| ------- | ----- |
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as failed and no further automatic retries occur. You can manually resend failed webhooks from the dashboard. | Provider | Total attempts | Retry timing |
| -------- | -------------- | ------------ |
| Local (default) | 4 | Back-to-back, with no backoff |
| BullMQ | 3 | Exponential backoff starting at 1 second |
| Inngest | 5 | Inngest platform backoff |
Only the individual delivery (`WebhookCall`) record is marked as failed. Documenso does not automatically disable the webhook or apply a circuit breaker, so future matching events continue to be delivered. After automatic attempts are exhausted, you can manually resend a failed delivery from the dashboard.
<Callout type="warn"> <Callout type="warn">
If your endpoint consistently fails, consider reviewing your server logs and ensuring your endpoint meets all [URL requirements](#webhook-url-requirements). If your endpoint consistently fails, consider reviewing your server logs and ensuring your endpoint meets all [URL requirements](#webhook-url-requirements).
@@ -255,6 +255,7 @@ const validEvents = [
'DOCUMENT_REJECTED', 'DOCUMENT_REJECTED',
'DOCUMENT_CANCELLED', 'DOCUMENT_CANCELLED',
'DOCUMENT_REMINDER_SENT', 'DOCUMENT_REMINDER_SENT',
'RECIPIENT_EXPIRED',
'TEMPLATE_CREATED', 'TEMPLATE_CREATED',
'TEMPLATE_UPDATED', 'TEMPLATE_UPDATED',
'TEMPLATE_DELETED', 'TEMPLATE_DELETED',
@@ -8,13 +8,14 @@ export const cancelEnvelopeMeta: TrpcRouteMeta = {
method: 'POST', method: 'POST',
path: '/envelope/cancel', path: '/envelope/cancel',
summary: 'Cancel envelope', summary: 'Cancel envelope',
description: 'Cancel a pending envelope',
tags: ['Envelope'], tags: ['Envelope'],
}, },
}; };
export const ZCancelEnvelopeRequestSchema = z.object({ export const ZCancelEnvelopeRequestSchema = z.object({
envelopeId: z.string(), envelopeId: z.string().describe('The ID of the envelope to cancel.'),
reason: z.string().optional(), reason: z.string().describe('The reason for cancelling the envelope.').optional(),
}); });
export const ZCancelEnvelopeResponseSchema = ZSuccessResponseSchema; export const ZCancelEnvelopeResponseSchema = ZSuccessResponseSchema;
@@ -8,12 +8,13 @@ export const deleteEnvelopeMeta: TrpcRouteMeta = {
method: 'POST', method: 'POST',
path: '/envelope/delete', path: '/envelope/delete',
summary: 'Delete envelope', summary: 'Delete envelope',
description: 'Delete an envelope',
tags: ['Envelope'], tags: ['Envelope'],
}, },
}; };
export const ZDeleteEnvelopeRequestSchema = z.object({ export const ZDeleteEnvelopeRequestSchema = z.object({
envelopeId: z.string(), envelopeId: z.string().describe('The ID of the envelope to delete.'),
}); });
export const ZDeleteEnvelopeResponseSchema = ZSuccessResponseSchema; export const ZDeleteEnvelopeResponseSchema = ZSuccessResponseSchema;
@@ -29,7 +29,17 @@ export const updateEnvelopeFieldsRoute = authenticatedProcedure
id: envelopeId, id: envelopeId,
}, },
type: null, type: null,
fields, fields: fields.map((field) => ({
id: field.id,
type: field.type,
pageNumber: field.page,
pageX: field.positionX,
pageY: field.positionY,
width: field.width,
height: field.height,
fieldMeta: field.fieldMeta,
envelopeItemId: field.envelopeItemId,
})),
requestMetadata: ctx.metadata, requestMetadata: ctx.metadata,
}); });
@@ -12,24 +12,32 @@ export const updateEnvelopeMeta: TrpcRouteMeta = {
method: 'POST', method: 'POST',
path: '/envelope/update', path: '/envelope/update',
summary: 'Update envelope', summary: 'Update envelope',
description: 'Update envelope properties and settings',
tags: ['Envelope'], tags: ['Envelope'],
}, },
}; };
export const ZUpdateEnvelopeRequestSchema = z.object({ export const ZUpdateEnvelopeRequestSchema = z.object({
envelopeId: z.string(), envelopeId: z.string().describe('The ID of the envelope to update.'),
data: z data: z
.object({ .object({
title: ZDocumentTitleSchema.optional(), title: ZDocumentTitleSchema.optional(),
externalId: ZDocumentExternalIdSchema.nullish(), externalId: ZDocumentExternalIdSchema.nullish(),
visibility: ZDocumentVisibilitySchema.optional(), visibility: ZDocumentVisibilitySchema.optional(),
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(), globalAccessAuth: z
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(), .array(ZDocumentAccessAuthTypesSchema)
folderId: z.string().nullish(), .describe('The authentication methods required to access the envelope.')
templateType: z.nativeEnum(TemplateType).optional(), .optional(),
globalActionAuth: z
.array(ZDocumentActionAuthTypesSchema)
.describe('The authentication methods required to sign the envelope.')
.optional(),
folderId: z.string().describe('The ID of the folder containing the envelope.').nullish(),
templateType: z.nativeEnum(TemplateType).describe('The template type.').optional(),
}) })
.describe('The envelope properties to update.')
.optional(), .optional(),
meta: ZDocumentMetaUpdateSchema.optional(), meta: ZDocumentMetaUpdateSchema.describe('The email and signing settings to update.').optional(),
}); });
export const ZUpdateEnvelopeResponseSchema = ZEnvelopeLiteSchema; export const ZUpdateEnvelopeResponseSchema = ZEnvelopeLiteSchema;