Compare commits

...
Author SHA1 Message Date
ephraimduncan f353706e24 docs(api): filter team document lists by type
GET /envelope without type returns templates as well as documents; add type=DOCUMENT
to every document-list example so counts and results match the prose
2026-07-30 22:04:01 +00:00
ephraimduncan eeae0e1e02 docs(api): make templates and teams pages envelope-first
- lead templates page with POST /envelope/use and /envelope/distribute; label /template/* deprecated
- legacy /template/use returns no signingUrl; document the real responses
- teams page: /team/* REST endpoints are not exposed; reframe around team-scoped tokens
- fix fabricated pagination wrappers; replace false Teams API card on the index page
2026-07-30 22:04:01 +00:00
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
17 changed files with 825 additions and 292 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();
@@ -60,8 +60,8 @@ Authorization: api_xxxxxxxxxxxxxxxx
href="/docs/developers/api/templates" href="/docs/developers/api/templates"
/> />
<Card <Card
title="Teams" title="Team-scoped access"
description="Manage teams and team members." description="Use team-scoped API tokens with envelope endpoints."
href="/docs/developers/api/teams" href="/docs/developers/api/teams"
/> />
</Cards> </Cards>
@@ -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.
--- ---
+29 -42
View File
@@ -1,44 +1,29 @@
--- ---
title: Teams API title: Team-Scoped API Access
description: Manage team resources, documents, and templates with team-scoped API tokens. description: Use team-scoped API tokens with document and template envelopes.
--- ---
import { Callout } from 'fumadocs-ui/components/callout'; import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Step, Steps } from 'fumadocs-ui/components/steps';
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).
</Callout> </Callout>
## Team Object ## Team Context
A team object contains the following properties: <Callout type="info">
The V2 REST API does not expose `/team/*` endpoints. Create and manage teams, members, and team
settings in the Documenso web application. This page explains how a team-scoped token applies
that team context to supported API resources.
</Callout>
| Property | Type | Description | The API resolves the team from your token. You do not pass a team ID when creating, listing, or
| ----------------- | -------------- | --------------------------------------------------- | using envelopes. The token's team ID determines which resources the request can access.
| `id` | number | Unique team identifier |
| `name` | string | Team display name |
| `url` | string | Unique team URL slug |
| `createdAt` | string | ISO 8601 timestamp |
| `avatarImageId` | string \| null | ID of the team's avatar image |
| `organisationId` | string | ID of the parent organisation |
| `currentTeamRole` | string | Your role in the team: `ADMIN`, `MANAGER`, `MEMBER` |
### Example Team Object
```json
{
"id": 123,
"name": "Engineering",
"url": "engineering",
"createdAt": "2025-01-15T10:30:00.000Z",
"avatarImageId": null,
"organisationId": "org_abc123",
"currentTeamRole": "ADMIN"
}
```
## Team-Scoped API Tokens ## Team-Scoped API Tokens
@@ -156,26 +141,26 @@ Retrieve all documents belonging to the team:
<Tab value="curl"> <Tab value="curl">
```bash ```bash
# List all team documents # List all team documents
curl -X GET "https://app.documenso.com/api/v2/envelope" \ curl -X GET "https://app.documenso.com/api/v2/envelope?type=DOCUMENT" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" -H "Authorization: api_team_xxxxxxxxxxxxxxxx"
# Filter by status # Filter by status
curl -X GET "https://app.documenso.com/api/v2/envelope?status=PENDING" \ curl -X GET "https://app.documenso.com/api/v2/envelope?type=DOCUMENT&status=PENDING" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" -H "Authorization: api_team_xxxxxxxxxxxxxxxx"
```` ````
</Tab> </Tab>
<Tab value="TypeScript"> <Tab value="TypeScript">
```typescript ```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope', { const response = await fetch('https://app.documenso.com/api/v2/envelope?type=DOCUMENT', {
method: 'GET', method: 'GET',
headers: { headers: {
Authorization: TEAM_API_TOKEN, Authorization: TEAM_API_TOKEN,
}, },
}); });
const { data, pagination } = await response.json(); const { data, count } = await response.json();
console.log(`Found ${pagination.totalItems} team documents`); console.log(`Found ${count} team documents`);
```` ````
</Tab> </Tab>
@@ -190,10 +175,11 @@ Templates created with a team token are shared across the team.
<Tabs items={['curl', 'TypeScript']}> <Tabs items={['curl', 'TypeScript']}>
<Tab value="curl"> <Tab value="curl">
```bash ```bash
curl -X POST "https://app.documenso.com/api/v2/template/create" \ curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" \ -H "Authorization: api_team_xxxxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \ -H "Content-Type: multipart/form-data" \
-F 'payload={ -F 'payload={
"type": "TEMPLATE",
"title": "NDA Template", "title": "NDA Template",
"recipients": [ "recipients": [
{ {
@@ -223,6 +209,7 @@ curl -X POST "https://app.documenso.com/api/v2/template/create" \
const form = new FormData(); const form = new FormData();
const payload = { const payload = {
type: 'TEMPLATE',
title: 'NDA Template', title: 'NDA Template',
recipients: [ recipients: [
{ {
@@ -249,7 +236,7 @@ form.append('files', fs.createReadStream('./nda-template.pdf'), {
contentType: 'application/pdf', contentType: 'application/pdf',
}); });
const response = await fetch('https://app.documenso.com/api/v2/template/create', { const response = await fetch('https://app.documenso.com/api/v2/envelope/create', {
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: TEAM_API_TOKEN, Authorization: TEAM_API_TOKEN,
@@ -257,8 +244,8 @@ const response = await fetch('https://app.documenso.com/api/v2/template/create',
body: form, body: form,
}); });
const template = await response.json(); const { id } = await response.json();
console.log('Created team template:', template.id); console.log('Created team template envelope:', id);
```` ````
</Tab> </Tab>
</Tabs> </Tabs>
@@ -268,14 +255,14 @@ console.log('Created team template:', template.id);
<Tabs items={['curl', 'TypeScript']}> <Tabs items={['curl', 'TypeScript']}>
<Tab value="curl"> <Tab value="curl">
```bash ```bash
curl -X GET "https://app.documenso.com/api/v2/template" \ curl -X GET "https://app.documenso.com/api/v2/envelope?type=TEMPLATE" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" -H "Authorization: api_team_xxxxxxxxxxxxxxxx"
```` ````
</Tab> </Tab>
<Tab value="TypeScript"> <Tab value="TypeScript">
```typescript ```typescript
const response = await fetch('https://app.documenso.com/api/v2/template', { const response = await fetch('https://app.documenso.com/api/v2/envelope?type=TEMPLATE', {
method: 'GET', method: 'GET',
headers: { headers: {
Authorization: TEAM_API_TOKEN, Authorization: TEAM_API_TOKEN,
@@ -330,19 +317,19 @@ const SALES_TEAM_TOKEN = process.env.SALES_TEAM_API_TOKEN;
const LEGAL_TEAM_TOKEN = process.env.LEGAL_TEAM_API_TOKEN; const LEGAL_TEAM_TOKEN = process.env.LEGAL_TEAM_API_TOKEN;
// Get pending documents from sales team // Get pending documents from sales team
const salesResponse = await fetch('https://app.documenso.com/api/v2/envelope?status=PENDING', { const salesResponse = await fetch('https://app.documenso.com/api/v2/envelope?type=DOCUMENT&status=PENDING', {
headers: { Authorization: SALES_TEAM_TOKEN }, headers: { Authorization: SALES_TEAM_TOKEN },
}); });
const salesDocs = await salesResponse.json(); const salesDocs = await salesResponse.json();
// Get completed documents from legal team // Get completed documents from legal team
const legalResponse = await fetch('https://app.documenso.com/api/v2/envelope?status=COMPLETED', { const legalResponse = await fetch('https://app.documenso.com/api/v2/envelope?type=DOCUMENT&status=COMPLETED', {
headers: { Authorization: LEGAL_TEAM_TOKEN }, headers: { Authorization: LEGAL_TEAM_TOKEN },
}); });
const legalDocs = await legalResponse.json(); const legalDocs = await legalResponse.json();
console.log(`Sales team: ${salesDocs.pagination.totalItems} pending`); console.log(`Sales team: ${salesDocs.count} pending`);
console.log(`Legal team: ${legalDocs.pagination.totalItems} completed`); console.log(`Legal team: ${legalDocs.count} completed`);
``` ```
## Error Responses ## Error Responses
@@ -13,7 +13,194 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
see the [OpenAPI Reference](https://openapi.documenso.com). see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout> </Callout>
## Template Object ## Use a Template Envelope
New integrations should create a document from a template envelope with the Envelope API.
```
POST /envelope/use
Content-Type: multipart/form-data
```
The request uses `multipart/form-data`:
| Part | Type | Required | Description |
| --------- | ------- | -------- | ------------------------------------------------------------------ |
| `payload` | JSON | Yes | Template envelope ID, recipient details, and document settings |
| `files` | File(s) | No | Replacement PDFs referenced by entries in `customDocumentData` |
### Payload Schema
| Field | Type | Required | Description |
| -------------------- | ------- | -------- | ------------------------------------------------------------------------ |
| `envelopeId` | string | Yes | ID of the template envelope |
| `externalId` | string | No | Your identifier for the created document envelope |
| `recipients` | array | No | Recipient details mapped to recipients in the template |
| `distributeDocument` | boolean | No | If `true`, create the document as pending and distribute it |
| `customDocumentData` | array | No | Maps uploaded replacement PDFs to template envelope items |
| `folderId` | string | No | Folder in which to create the document |
| `prefillFields` | array | No | Field values to prefill before distribution |
| `override` | object | No | Template values to override for the created document |
| `attachments` | array | No | Link attachments to add to the document |
| `formValues` | object | No | PDF form values to apply |
Each recipient entry accepts the following fields:
| Field | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------------------- |
| `id` | number | Yes | Recipient ID from the template envelope |
| `email` | string | Yes | Recipient email address |
| `name` | string | No | Recipient display name |
| `signingOrder` | number | No | Recipient position in sequential signing |
Each `customDocumentData` entry maps an uploaded file to a template item:
| Field | Type | Required | Description |
| ---------------- | ---------------- | -------- | --------------------------------------------------------------- |
| `identifier` | string \| number | Yes | Uploaded filename or zero-based file index |
| `envelopeItemId` | string | Yes | Template envelope item whose PDF the uploaded file replaces |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/use" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-F 'payload={
"envelopeId": "envelope_template123",
"externalId": "contract-2025-001",
"recipients": [
{
"id": 1,
"email": "john.doe@example.com",
"name": "John Doe"
}
],
"prefillFields": [
{
"id": 101,
"type": "text",
"value": "Senior Software Engineer"
}
],
"distributeDocument": false
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const form = new FormData();
form.append(
'payload',
JSON.stringify({
envelopeId: 'envelope_template123',
externalId: 'contract-2025-001',
recipients: [
{
id: 1,
email: 'john.doe@example.com',
name: 'John Doe',
},
],
prefillFields: [
{
id: 101,
type: 'text',
value: 'Senior Software Engineer',
},
],
distributeDocument: false,
}),
);
const response = await fetch('https://app.documenso.com/api/v2/envelope/use', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
body: form,
});
const document = await response.json();
console.log('Created document envelope:', document.id);
```
</Tab>
</Tabs>
### Response
```json
{
"id": "envelope_document123",
"recipients": [
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"token": "recipient_token",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/recipient_token"
}
]
}
```
### Distribute the Created Envelope
If you leave `distributeDocument` unset or set it to `false`, distribute the created document with
`POST /envelope/distribute`. Its response confirms delivery and includes each recipient's signing URL.
```typescript
const distributionResponse = await fetch(
'https://app.documenso.com/api/v2/envelope/distribute',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: document.id,
}),
},
);
const distribution = await distributionResponse.json();
console.log('Signing URL:', distribution.recipients[0].signingUrl);
```
```json
{
"success": true,
"id": "envelope_document123",
"recipients": [
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"token": "recipient_token",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/recipient_token"
}
]
}
```
---
## Deprecated Template Endpoint Reference
<Callout type="warn">
Every `/template/*` endpoint below is deprecated. Use the Envelope API for new integrations and
follow [Migrating to Envelopes](/docs/developers/api/migrate-to-envelopes) to replace existing calls.
The legacy reference remains here to support migrations.
</Callout>
## Legacy Template Object
A template object contains the following properties: A template object contains the following properties:
@@ -91,7 +278,7 @@ A template object contains the following properties:
} }
``` ```
## List Templates ## List Templates (Deprecated)
Retrieve a paginated list of templates. Retrieve a paginated list of templates.
@@ -139,8 +326,8 @@ const response = await fetch(`${BASE_URL}/template`, {
}, },
}); });
const { data, pagination } = await response.json(); const { data, count } = await response.json();
console.log(`Found ${pagination.totalItems} templates`); console.log(`Found ${count} templates`);
// Filter by type // Filter by type
const privateResponse = await fetch( const privateResponse = await fetch(
@@ -181,18 +368,16 @@ const privateTemplates = await privateResponse.json();
] ]
} }
], ],
"pagination": { "count": 25,
"page": 1, "currentPage": 1,
"perPage": 10, "perPage": 10,
"totalPages": 3, "totalPages": 3
"totalItems": 25
}
} }
``` ```
--- ---
## Get Template ## Get Template (Deprecated)
Retrieve a single template by ID. Retrieve a single template by ID.
@@ -238,9 +423,9 @@ Returns the full template object including recipients, fields, and metadata.
--- ---
## Create Document from Template ## Create Document from Template (Deprecated)
Create a new document using a template. This is the primary way to use templates programmatically. Create a new document using the deprecated template endpoint.
<Callout type="info"> <Callout type="info">
This endpoint does not support [PDF placeholder parsing](/docs/users/documents/advanced/pdf-placeholders). Use `POST /envelope/create` for placeholder-based field positioning. This endpoint does not support [PDF placeholder parsing](/docs/users/documents/advanced/pdf-placeholders). Use `POST /envelope/create` for placeholder-based field positioning.
@@ -415,32 +600,57 @@ const prefilledDocument = await prefillResponse.json();
### Response ### Response
Returns the created document object with recipients and signing URLs. The endpoint returns the full legacy document object. The selected fields below show both the numeric
legacy `id` and canonical `envelopeId`. Recipient entries do not include a `signingUrl`.
```json ```json
{ {
"id": "envelope_xyz789", "id": 789,
"type": "DOCUMENT", "envelopeId": "envelope_xyz789",
"status": "PENDING", "status": "PENDING",
"title": "Employment Contract",
"source": "TEMPLATE", "source": "TEMPLATE",
"title": "Employment Contract",
"externalId": "contract-2025-001", "externalId": "contract-2025-001",
"recipients": [ "recipients": [
{ {
"id": 1, "id": 1,
"envelopeId": "envelope_xyz789",
"documentId": 789,
"templateId": null,
"email": "john.doe@example.com", "email": "john.doe@example.com",
"name": "John Doe", "name": "John Doe",
"role": "SIGNER", "role": "SIGNER",
"signingStatus": "NOT_SIGNED", "signingStatus": "NOT_SIGNED",
"signingUrl": "https://app.documenso.com/sign/abc123" "signingOrder": 1
} }
] ]
} }
```` ```
To send a document created with `distributeDocument: false` and receive signing links, call
`POST /envelope/distribute` with its `envelopeId`:
```typescript
const document = await response.json();
const distributionResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: document.envelopeId,
}),
});
const distribution = await distributionResponse.json();
console.log('Signing URL:', distribution.recipients[0].signingUrl);
```
--- ---
## Override Template Settings ## Override Template Settings (Deprecated)
When creating a document from a template, you can override various settings: When creating a document from a template, you can override various settings:
@@ -488,7 +698,7 @@ const response = await fetch(`${BASE_URL}/template/use`, {
--- ---
## Prefill Fields ## Prefill Fields (Deprecated)
Prefill field values when creating a document from a template. This is useful for populating known data before sending. Prefill field values when creating a document from a template. This is useful for populating known data before sending.
@@ -577,7 +787,7 @@ const response = await fetch(`${BASE_URL}/template/use`, {
--- ---
## Update Template ## Update Template (Deprecated)
Update a template's properties. Update a template's properties.
@@ -643,7 +853,7 @@ const template = await response.json();
--- ---
## Duplicate Template ## Duplicate Template (Deprecated)
Create a copy of an existing template. Create a copy of an existing template.
@@ -695,7 +905,7 @@ console.log('New template ID:', duplicatedTemplate.id);
--- ---
## Delete Template ## Delete Template (Deprecated)
Delete a template. Delete a template.
@@ -754,7 +964,7 @@ const { success } = await response.json();
--- ---
## Direct Link Templates ## Direct Link Templates (Deprecated)
Direct link templates allow recipients to create and sign documents without requiring you to explicitly create each document. When a recipient visits the direct link, a new document is automatically created from the template. Direct link templates allow recipients to create and sign documents without requiring you to explicitly create each document. When a recipient visits the direct link, a new document is automatically created from the template.
@@ -898,7 +1108,7 @@ const { success } = await response.json();
--- ---
## Custom Document Data ## Custom Document Data (Deprecated)
When creating a document from a template, you can replace the template's PDF with a custom PDF by using the `customDocumentData` parameter. This is useful when you need to generate the PDF dynamically while reusing the template's recipient and field configuration. When creating a document from a template, you can replace the template's PDF with a custom PDF by using the `customDocumentData` parameter. This is useful when you need to generate the PDF dynamically while reusing the template's recipient and field configuration.
@@ -913,7 +1123,7 @@ See the [OpenAPI Reference](https://openapi.documenso.com) for the full request
--- ---
## Template Types ## Template Types (Legacy)
| Type | Description | | Type | Description |
| --------- | ------------------------------------------------------------------ | | --------- | ------------------------------------------------------------------ |
@@ -922,7 +1132,7 @@ See the [OpenAPI Reference](https://openapi.documenso.com) for the full request
--- ---
## Complete Example: Contract Workflow ## Complete Legacy Example: Contract Workflow (Deprecated)
This example demonstrates a complete workflow for using templates to send contracts. This example demonstrates a complete workflow for using templates to send contracts.
@@ -996,16 +1206,29 @@ async function sendEmploymentContract(employeeData: {
subject: `Employment Contract for ${employeeData.name}`, subject: `Employment Contract for ${employeeData.name}`,
message: `Hi ${employeeData.name},\n\nPlease review and sign your employment contract.`, message: `Hi ${employeeData.name},\n\nPlease review and sign your employment contract.`,
}, },
distributeDocument: true, distributeDocument: false,
externalId: `emp-contract-${Date.now()}`, externalId: `emp-contract-${Date.now()}`,
}), }),
}); });
const document = await documentResponse.json(); const document = await documentResponse.json();
// 5. Distribute the envelope and get recipient signing links
const distributionResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: document.envelopeId,
}),
});
const distribution = await distributionResponse.json();
return { return {
documentId: document.id, envelopeId: document.envelopeId,
signingUrl: document.recipients[0].signingUrl, signingUrl: distribution.recipients[0].signingUrl,
}; };
} }
@@ -1018,7 +1241,7 @@ const result = await sendEmploymentContract({
startDate: '2025-03-01', startDate: '2025-03-01',
}); });
console.log('Document created:', result.documentId); console.log('Document created:', result.envelopeId);
console.log('Signing URL:', result.signingUrl); console.log('Signing URL:', result.signingUrl);
```` ````
@@ -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;