Compare commits

...
Author SHA1 Message Date
ephraimduncan a43d09be9b docs(api): document placeholder positioning and per-entry field options 2026-07-31 12:56:28 +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
13 changed files with 539 additions and 184 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++;
} }
+193 -88
View File
@@ -6,6 +6,8 @@ description: Add signature and form fields to documents via API.
import { Callout } from 'fumadocs-ui/components/callout'; import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<EnvelopeWarning />
<Callout type="warn"> <Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference, This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com). see the [OpenAPI Reference](https://openapi.documenso.com).
@@ -19,13 +21,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 +40,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"
} }
} }
``` ```
@@ -61,7 +64,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
| Type | Description | Auto-filled | | Type | Description | Auto-filled |
| ---------------- | ----------------------------------------- | ----------- | | ---------------- | ----------------------------------------- | ----------- |
| `SIGNATURE` | Drawn, typed, or uploaded signature | No | | `SIGNATURE` | Drawn, typed, or uploaded signature | No |
| `FREE_SIGNATURE` | Unrestricted signature without validation | No | | `FREE_SIGNATURE` | Legacy free-form signature. Accepted by the v2 create schema but rejected by the v1 API and unsupported in the signing UI — avoid in new integrations | No |
| `INITIALS` | Recipient's initials | No | | `INITIALS` | Recipient's initials | No |
| `NAME` | Recipient's full name | Yes | | `NAME` | Recipient's full name | Yes |
| `EMAIL` | Recipient's email address | Yes | | `EMAIL` | Recipient's email address | Yes |
@@ -134,10 +137,12 @@ 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 |
Each entry in `data` requires a `type`, a `recipientId`, and a position — either explicit coordinates (`page`, `positionX`, `positionY`, `width`, `height`) or a [text placeholder](#placeholder-based-field-positioning) (`placeholder` with optional `width`, `height`, and `matchAll`). Optional per-entry properties: `envelopeItemId` (which PDF in the envelope to place the field on; defaults to the first item) and `fieldMeta`.
### Code Examples ### Code Examples
@@ -148,32 +153,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 +204,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 +244,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 +255,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 +336,10 @@ 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 |
Each entry in `data` requires the field `id` and `type`. Position properties (`page`, `positionX`, `positionY`, `width`, `height`), `envelopeItemId`, and `fieldMeta` are optional — only supplied values are updated. Placeholder positioning is not supported when updating; use coordinates.
### Code Examples ### Code Examples
@@ -311,17 +350,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 +377,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 +396,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 +521,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 +535,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
}; };
@@ -479,6 +557,33 @@ This approach is useful when generating PDFs programmatically or using templates
See the [PDF Placeholders](/docs/users/documents/advanced/pdf-placeholders) guide for the full placeholder format reference, including supported field types, recipient identifiers, and field options. See the [PDF Placeholders](/docs/users/documents/advanced/pdf-placeholders) guide for the full placeholder format reference, including supported field types, recipient identifiers, and field options.
### Placeholder Positioning via the API
`POST /envelope/field/create-many` accepts a placeholder position in place of coordinates. Instead of `page`, `positionX`, `positionY`, `width`, and `height`, pass:
| Field | Type | Required | Description |
| ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `placeholder` | string | Yes | Text to search for in the PDF (e.g. `{{name}}`). The field is placed at the bounding box of the first match. |
| `width` | number | No | Override the field width. Defaults to the width of the matched text. |
| `height` | number | No | Override the field height. Defaults to the height of the matched text. |
| `matchAll` | boolean | No | Create a field at every occurrence of the placeholder instead of only the first. |
```json
{
"envelopeId": "envelope_abcdefhiklmnorst",
"data": [
{
"type": "SIGNATURE",
"recipientId": 456,
"placeholder": "{{signature}}",
"matchAll": true
}
]
}
```
`POST /envelope/field/update-many` does not accept placeholders — field updates are coordinate-only.
--- ---
## Field Meta Options ## Field Meta Options
@@ -643,15 +748,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 +768,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 +782,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 +796,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 +815,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;
} }
@@ -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;