diff --git a/apps/docs/content/docs/developers/api/common-errors.mdx b/apps/docs/content/docs/developers/api/common-errors.mdx
index f26eba13c..91fcb9313 100644
--- a/apps/docs/content/docs/developers/api/common-errors.mdx
+++ b/apps/docs/content/docs/developers/api/common-errors.mdx
@@ -15,6 +15,8 @@ This guide provides a comprehensive troubleshooting matrix for the standard erro
| `INVALID_REQUEST` | The overall request is malformed or invalid. | Review your API call parameters, including the URL, query parameters, and headers. Correct the request syntax. |
| `RECIPIENT_EXPIRED` | The signing link or recipient access has expired. | Generate and resend a new invitation to the affected recipient. |
| `LIMIT_EXCEEDED` | Your account usage quota has been exceeded. | Check your current plan limits. Upgrade your subscription or wait until your billing cycle renews. |
+| `MISSING_ENV_VAR` | A required environment variable is not configured on the server (500). | Primarily affects self-hosted instances: set the environment variable named in the error message and restart. On Documenso Cloud, contact support. |
+| `MISSING_SIGNATURE_FIELD` | A signer has no signature field placed on the document (400). Returned when distributing an envelope. | Add at least one signature field for every recipient with a signing role before calling `/envelope/distribute`. |
| `NOT_FOUND` | The requested resource could not be found (404). | Verify the resource ID (envelope, document, webhook) passed in the URL. Ensure the resource has not been deleted. |
| `NOT_IMPLEMENTED` | The requested feature is not currently supported by the server. | Consult the API documentation to verify available methods. Do not use this endpoint at this time. |
| `NOT_SETUP` | The required configuration for this action is incomplete. | Access your account or integration settings and complete the necessary configuration before retrying. |
@@ -37,7 +39,28 @@ The following errors occur when attempting to perform actions on an envelope tha
| `ENVELOPE_DRAFT` | The action cannot be performed because the envelope is still in a draft state. | Finalize the envelope configuration and transition it to the `PENDING` (sent) state before attempting this operation. |
| `ENVELOPE_COMPLETED` | The action cannot be performed because the envelope is already completed. | No further modifications (e.g., adding signers, modifying documents) can be made to an envelope once the signing process is finished. |
| `ENVELOPE_REJECTED` | The action cannot be performed because the envelope was rejected by a recipient. | The signing flow is permanently halted. Create a new envelope if you wish to resubmit the document. |
+| `ENVELOPE_CANCELLED` | The action cannot be performed because the envelope was cancelled (400). | Create a new envelope if you need to restart the signing process. |
| `ENVELOPE_LEGACY` | The action cannot be performed because the envelope uses an obsolete format. | This envelope was created with a legacy version of the system. Recreate the envelope using the current API version to interact with it. |
+| `ENVELOPE_TSP_LOCKED` | An AES/QES envelope cannot be modified after it leaves the draft state (400). | Make changes while the envelope is in `DRAFT`, or create a new envelope. |
+
+## CSC Signing Errors
+
+These errors apply to Cloud Signature Consortium (CSC) signing flows.
+
+| Error Code | Description | Recommended Action |
+| :--- | :--- | :--- |
+| `CSC_INSTANCE_MODE_MISMATCH` | The requested signature level does not match the instance's CSC mode (400). | Use the signature level supported by the instance's signing configuration. |
+| `CSC_UNLICENSED` | CSC signing is not licensed for this instance (403). | Enable the CSC signing license before retrying. |
+| `CSC_PROVIDER_INFO_FAILED` | The CSC provider's discovery request failed or returned unusable information (500). | Check the provider URL, availability, and OAuth configuration. |
+| `CSC_PROVIDER_NO_TSA` | A timestamp authority is unavailable or unusable for CSC signing (500). | Configure `NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY` and verify provider timestamp access. |
+| `CSC_CREDENTIAL_LIST_EMPTY` | The CSC provider returned no signing credentials for the authenticated user (400). | Enrol a signing credential with the provider, then authenticate again. |
+| `CSC_CERT_INVALID` | The selected signing certificate is missing, invalid, or outside its validity period (400). | Select or renew a valid certificate, then authenticate again. |
+| `CSC_ALGORITHM_REFUSED` | The signing credential uses an unsupported key or digest algorithm (400). | Select a credential that satisfies the instance's CSC algorithm policy. |
+| `CSC_SAD_EXPIRED_PRE_SIGN` | The signature activation data is missing, expired, or unreadable before signing (400). | Repeat the credential authorization flow. |
+| `CSC_TSP_TIMEOUT` | The trust service provider did not complete the signing request before the timeout (408). | Retry the signing request after checking provider availability. |
+| `CSC_EMBED_FAILED` | The returned CSC signature could not be embedded into the envelope items (400). | Restart the signing attempt. If it fails again, contact support. |
+| `CSC_BASE_DOCUMENT_MUTATED` | The document changed between signature preparation and signing (500). | Restart signing from the current envelope state. |
+| `CSC_REQUEST_FAILED` | A CSC provider request failed without a more specific CSC error (500). | Check provider availability and configuration, then retry. |
## See Also
diff --git a/apps/docs/content/docs/developers/api/fields.mdx b/apps/docs/content/docs/developers/api/fields.mdx
index 71f680a62..0cec72339 100644
--- a/apps/docs/content/docs/developers/api/fields.mdx
+++ b/apps/docs/content/docs/developers/api/fields.mdx
@@ -6,6 +6,8 @@ description: Add signature and form fields to documents via API.
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+
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).
@@ -19,13 +21,13 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
| `secondaryId` | string | Secondary identifier for audit logs |
| `type` | string | Field type (see [Field Types](#field-types)) |
| `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 |
| `page` | number | Page number (1-indexed) |
-| `positionX` | number | X coordinate as percentage (0-100) |
-| `positionY` | number | Y coordinate as percentage (0-100) |
-| `width` | number | Width as percentage of page (0-100) |
-| `height` | number | Height as percentage of page (0-100) |
+| `positionX` | string | X coordinate as percentage (0-100), a decimal serialized as a string |
+| `positionY` | string | Y coordinate as percentage (0-100), a decimal serialized as a string |
+| `width` | string | Width as percentage of page (0-100), a decimal serialized as a string |
+| `height` | string | Height as percentage of page (0-100), a decimal serialized as a string |
| `customText` | string | Value entered by the recipient |
| `inserted` | boolean | Whether the field has been completed |
| `fieldMeta` | object \| null | Type-specific configuration options |
@@ -38,18 +40,19 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
"secondaryId": "field_abc123",
"type": "SIGNATURE",
"recipientId": 123,
- "envelopeId": 789,
- "envelopeItemId": "envelope_item_xyz",
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "envelopeItemId": "envelope_item_abcdefhiklmnorst",
"page": 1,
- "positionX": 10,
- "positionY": 80,
- "width": 30,
- "height": 5,
+ "positionX": "10",
+ "positionY": "80",
+ "width": "30",
+ "height": "5",
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "signature",
- "required": true
+ "required": true,
+ "overflow": "auto"
}
}
```
@@ -61,7 +64,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
| Type | Description | Auto-filled |
| ---------------- | ----------------------------------------- | ----------- |
| `SIGNATURE` | Drawn, typed, or uploaded signature | No |
-| `FREE_SIGNATURE` | Unrestricted signature without validation | No |
+| `FREE_SIGNATURE` | Legacy free-form signature. Accepted by the v2 create schema but rejected by the v1 API and unsupported in the signing UI — avoid in new integrations | No |
| `INITIALS` | Recipient's initials | No |
| `NAME` | Recipient's full name | Yes |
| `EMAIL` | Recipient's email address | Yes |
@@ -134,10 +137,12 @@ POST /envelope/field/create-many
### Request Body
-| Field | Type | Required | Description |
-| ----------- | ------ | -------- | ------------------------------- |
-| `documentId`| number | Yes | The document ID |
-| `fields` | array | Yes | Array of field configurations |
+| Field | Type | Required | Description |
+| ------------ | ------ | -------- | ------------------------------- |
+| `envelopeId` | string | Yes | The envelope ID |
+| `data` | array | Yes | Array of field configurations |
+
+Each entry in `data` requires a `type`, a `recipientId`, and a position — either explicit coordinates (`page`, `positionX`, `positionY`, `width`, `height`) or a [text placeholder](#placeholder-based-field-positioning) (`placeholder` with optional `width`, `height`, and `matchAll`). Optional per-entry properties: `envelopeItemId` (which PDF in the envelope to place the field on; defaults to the first item) and `fieldMeta`.
### Code Examples
@@ -148,32 +153,32 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/field/create-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
- "documentId": 123,
- "fields": [
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "data": [
{
"type": "SIGNATURE",
"recipientId": 456,
- "pageNumber": 1,
- "pageX": 10,
- "pageY": 80,
+ "page": 1,
+ "positionX": 10,
+ "positionY": 80,
"width": 30,
"height": 5
},
{
"type": "DATE",
"recipientId": 456,
- "pageNumber": 1,
- "pageX": 50,
- "pageY": 80,
+ "page": 1,
+ "positionX": 50,
+ "positionY": 80,
"width": 20,
"height": 3
},
{
"type": "TEXT",
"recipientId": 456,
- "pageNumber": 1,
- "pageX": 10,
- "pageY": 70,
+ "page": 1,
+ "positionX": 10,
+ "positionY": 70,
"width": 40,
"height": 4,
"fieldMeta": {
@@ -199,32 +204,32 @@ const response = await fetch(
'Content-Type': 'application/json',
},
body: JSON.stringify({
- documentId: 123,
- fields: [
+ envelopeId: 'envelope_abcdefhiklmnorst',
+ data: [
{
type: 'SIGNATURE',
recipientId: 456,
- pageNumber: 1,
- pageX: 10,
- pageY: 80,
+ page: 1,
+ positionX: 10,
+ positionY: 80,
width: 30,
height: 5,
},
{
type: 'DATE',
recipientId: 456,
- pageNumber: 1,
- pageX: 50,
- pageY: 80,
+ page: 1,
+ positionX: 50,
+ positionY: 80,
width: 20,
height: 3,
},
{
type: 'TEXT',
recipientId: 456,
- pageNumber: 1,
- pageX: 10,
- pageY: 70,
+ page: 1,
+ positionX: 10,
+ positionY: 70,
width: 40,
height: 4,
fieldMeta: {
@@ -239,8 +244,8 @@ const response = await fetch(
}
);
-const { fields } = await response.json();
-console.log(`Created ${fields.length} fields`);
+const { data } = await response.json();
+console.log(`Created ${data.length} fields`);
````
@@ -250,36 +255,68 @@ console.log(`Created ${fields.length} fields`);
```json
{
- "fields": [
+ "data": [
{
"id": 101,
+ "secondaryId": "field_abc123",
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "SIGNATURE",
"recipientId": 456,
"page": 1,
- "positionX": 10,
- "positionY": 80,
- "width": 30,
- "height": 5
+ "positionX": "10",
+ "positionY": "80",
+ "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": 80,
- "width": 20,
- "height": 3
+ "positionX": "50",
+ "positionY": "80",
+ "width": "20",
+ "height": "3",
+ "customText": "",
+ "inserted": false,
+ "fieldMeta": {
+ "type": "date",
+ "fontSize": 12,
+ "textAlign": "left",
+ "overflow": "auto"
+ }
},
{
"id": 103,
+ "secondaryId": "field_ghi789",
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "envelopeItemId": "envelope_item_abcdefhiklmnorst",
"type": "TEXT",
"recipientId": 456,
"page": 1,
- "positionX": 10,
- "positionY": 70,
- "width": 40,
- "height": 4
+ "positionX": "10",
+ "positionY": "70",
+ "width": "40",
+ "height": "4",
+ "customText": "",
+ "inserted": false,
+ "fieldMeta": {
+ "type": "text",
+ "label": "Job Title",
+ "placeholder": "Enter your job title",
+ "required": true
+ }
}
]
}
@@ -299,8 +336,10 @@ POST /envelope/field/update-many
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------- |
-| `documentId` | number | Yes | The document ID |
-| `fields` | array | Yes | Array of field update objects |
+| `envelopeId` | string | Yes | The envelope ID |
+| `data` | array | Yes | Array of field update objects |
+
+Each entry in `data` requires the field `id` and `type`. Position properties (`page`, `positionX`, `positionY`, `width`, `height`), `envelopeItemId`, and `fieldMeta` are optional — only supplied values are updated. Placeholder positioning is not supported when updating; use coordinates.
### Code Examples
@@ -311,17 +350,17 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/field/update-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
- "documentId": 123,
- "fields": [
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "data": [
{
"id": 101,
"type": "SIGNATURE",
- "pageY": 85
+ "positionY": 85
},
{
"id": 102,
"type": "DATE",
- "pageY": 85
+ "positionY": 85
}
]
}'
@@ -338,16 +377,16 @@ const response = await fetch(
'Content-Type': 'application/json',
},
body: JSON.stringify({
- documentId: 123,
- fields: [
- { id: 101, type: 'SIGNATURE', pageY: 85 },
- { id: 102, type: 'DATE', pageY: 85 },
+ envelopeId: 'envelope_abcdefhiklmnorst',
+ data: [
+ { id: 101, type: 'SIGNATURE', positionY: 85 },
+ { id: 102, type: 'DATE', positionY: 85 },
],
}),
}
);
-const { fields } = await response.json();
+const { data } = await response.json();
````
@@ -357,9 +396,48 @@ const { fields } = await response.json();
```json
{
- "fields": [
- { "id": 101, "type": "SIGNATURE", "positionY": 85 },
- { "id": 102, "type": "DATE", "positionY": 85 }
+ "data": [
+ {
+ "id": 101,
+ "secondaryId": "field_abc123",
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "envelopeItemId": "envelope_item_abcdefhiklmnorst",
+ "type": "SIGNATURE",
+ "recipientId": 456,
+ "page": 1,
+ "positionX": "10",
+ "positionY": "85",
+ "width": "30",
+ "height": "5",
+ "customText": "",
+ "inserted": false,
+ "fieldMeta": {
+ "type": "signature",
+ "fontSize": 18,
+ "overflow": "auto"
+ }
+ },
+ {
+ "id": 102,
+ "secondaryId": "field_def456",
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "envelopeItemId": "envelope_item_abcdefhiklmnorst",
+ "type": "DATE",
+ "recipientId": 456,
+ "page": 1,
+ "positionX": "50",
+ "positionY": "85",
+ "width": "20",
+ "height": "3",
+ "customText": "",
+ "inserted": false,
+ "fieldMeta": {
+ "type": "date",
+ "fontSize": 12,
+ "textAlign": "left",
+ "overflow": "auto"
+ }
+ }
]
}
````
@@ -443,8 +521,8 @@ Fields use percentage-based coordinates relative to the PDF page dimensions.
(0,0) ─────────────────────────── (100,0)
│ │
│ ┌─────────┐ │
- │ │ Field │ (pageX: 10, │
- │ │ │ pageY: 20, │
+ │ │ Field │ (positionX: 10, │
+ │ │ │ positionY: 20, │
│ └─────────┘ width: 30, │
│ height: 5) │
│ │
@@ -457,9 +535,9 @@ Fields use percentage-based coordinates relative to the PDF page dimensions.
const field = {
type: 'SIGNATURE',
recipientId: 123,
- pageNumber: 1,
- pageX: 60, // 60% from left
- pageY: 85, // 85% from top (near bottom)
+ page: 1,
+ positionX: 60, // 60% from left
+ positionY: 85, // 85% from top (near bottom)
width: 30, // 30% of page width
height: 8, // 8% of page height
};
@@ -479,6 +557,33 @@ This approach is useful when generating PDFs programmatically or using templates
See the [PDF Placeholders](/docs/users/documents/advanced/pdf-placeholders) guide for the full placeholder format reference, including supported field types, recipient identifiers, and field options.
+### Placeholder Positioning via the API
+
+`POST /envelope/field/create-many` accepts a placeholder position in place of coordinates. Instead of `page`, `positionX`, `positionY`, `width`, and `height`, pass:
+
+| Field | Type | Required | Description |
+| ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------ |
+| `placeholder` | string | Yes | Text to search for in the PDF (e.g. `{{name}}`). The field is placed at the bounding box of the first match. |
+| `width` | number | No | Override the field width. Defaults to the width of the matched text. |
+| `height` | number | No | Override the field height. Defaults to the height of the matched text. |
+| `matchAll` | boolean | No | Create a field at every occurrence of the placeholder instead of only the first. |
+
+```json
+{
+ "envelopeId": "envelope_abcdefhiklmnorst",
+ "data": [
+ {
+ "type": "SIGNATURE",
+ "recipientId": 456,
+ "placeholder": "{{signature}}",
+ "matchAll": true
+ }
+ ]
+}
+```
+
+`POST /envelope/field/update-many` does not accept placeholders — field updates are coordinate-only.
+
---
## Field Meta Options
@@ -643,15 +748,15 @@ All field types support these base options:
Create a document with a signature block containing multiple field types:
```typescript
-async function addSignatureBlock(documentId: number, recipientId: number) {
- const fields = [
+async function addSignatureBlock(envelopeId: string, recipientId: number) {
+ const data = [
// Signature
{
type: 'SIGNATURE',
recipientId,
- pageNumber: 1,
- pageX: 10,
- pageY: 80,
+ page: 1,
+ positionX: 10,
+ positionY: 80,
width: 30,
height: 8,
fieldMeta: {
@@ -663,9 +768,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{
type: 'NAME',
recipientId,
- pageNumber: 1,
- pageX: 10,
- pageY: 90,
+ page: 1,
+ positionX: 10,
+ positionY: 90,
width: 30,
height: 4,
fieldMeta: {
@@ -677,9 +782,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{
type: 'DATE',
recipientId,
- pageNumber: 1,
- pageX: 50,
- pageY: 80,
+ page: 1,
+ positionX: 50,
+ positionY: 80,
width: 20,
height: 4,
fieldMeta: {
@@ -691,9 +796,9 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
{
type: 'TEXT',
recipientId,
- pageNumber: 1,
- pageX: 50,
- pageY: 90,
+ page: 1,
+ positionX: 50,
+ positionY: 90,
width: 30,
height: 4,
fieldMeta: {
@@ -710,7 +815,7 @@ async function addSignatureBlock(documentId: number, recipientId: number) {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
- body: JSON.stringify({ documentId, fields }),
+ body: JSON.stringify({ envelopeId, data }),
});
return response.json();
diff --git a/apps/docs/content/docs/developers/api/index.mdx b/apps/docs/content/docs/developers/api/index.mdx
index e8d7139eb..ac64de966 100644
--- a/apps/docs/content/docs/developers/api/index.mdx
+++ b/apps/docs/content/docs/developers/api/index.mdx
@@ -60,8 +60,8 @@ Authorization: api_xxxxxxxxxxxxxxxx
href="/docs/developers/api/templates"
/>
diff --git a/apps/docs/content/docs/developers/api/recipients.mdx b/apps/docs/content/docs/developers/api/recipients.mdx
index 6751a400a..bb72afb92 100644
--- a/apps/docs/content/docs/developers/api/recipients.mdx
+++ b/apps/docs/content/docs/developers/api/recipients.mdx
@@ -6,6 +6,8 @@ description: Add and manage envelope recipients via API.
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+
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).
@@ -16,7 +18,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
```json
{
"id": 123,
- "envelopeId": "clu1abc2def3ghi4jkl",
+ "envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -134,7 +136,7 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/recipient/create-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
- "envelopeId": "clu1abc2def3ghi4jkl",
+ "envelopeId": "envelope_abcdefhiklmnorst",
"data": [
{
"email": "signer@example.com",
@@ -164,7 +166,7 @@ const response = await fetch(
'Content-Type': 'application/json',
},
body: JSON.stringify({
- envelopeId: 'clu1abc2def3ghi4jkl',
+ envelopeId: 'envelope_abcdefhiklmnorst',
data: [
{
email: 'signer@example.com',
@@ -196,7 +198,7 @@ const { data: recipients } = await response.json();
"data": [
{
"id": 789,
- "envelopeId": "clu1abc2def3ghi4jkl",
+ "envelopeId": "envelope_abcdefhiklmnorst",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
@@ -209,7 +211,7 @@ const { data: recipients } = await response.json();
},
{
"id": 790,
- "envelopeId": "clu1abc2def3ghi4jkl",
+ "envelopeId": "envelope_abcdefhiklmnorst",
"email": "approver@example.com",
"name": "Jane Smith",
"role": "APPROVER",
@@ -262,7 +264,7 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/recipient/update-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
- "envelopeId": "clu1abc2def3ghi4jkl",
+ "envelopeId": "envelope_abcdefhiklmnorst",
"data": [
{
"id": 789,
@@ -284,7 +286,7 @@ const response = await fetch(
'Content-Type': 'application/json',
},
body: JSON.stringify({
- envelopeId: 'clu1abc2def3ghi4jkl',
+ envelopeId: 'envelope_abcdefhiklmnorst',
data: [
{
id: 789,
@@ -387,7 +389,7 @@ const response = await fetch('https://app.documenso.com/api/v2/envelope/recipien
'Content-Type': 'application/json',
},
body: JSON.stringify({
- envelopeId: 'clu1abc2def3ghi4jkl',
+ envelopeId: 'envelope_abcdefhiklmnorst',
data: [
{
email: 'approver@example.com',
@@ -462,7 +464,7 @@ const response = await fetch('https://app.documenso.com/api/v2/envelope/recipien
'Content-Type': 'application/json',
},
body: JSON.stringify({
- envelopeId: 'clu1abc2def3ghi4jkl',
+ envelopeId: 'envelope_abcdefhiklmnorst',
data: [
{
email: 'signer@example.com',
diff --git a/apps/docs/content/docs/developers/api/teams.mdx b/apps/docs/content/docs/developers/api/teams.mdx
index 0d869c56c..258e015c0 100644
--- a/apps/docs/content/docs/developers/api/teams.mdx
+++ b/apps/docs/content/docs/developers/api/teams.mdx
@@ -1,44 +1,29 @@
---
-title: Teams API
-description: Manage team resources, documents, and templates with team-scoped API tokens.
+title: Team-Scoped API Access
+description: Use team-scoped API tokens with document and template envelopes.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+
+
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).
-## Team Object
+## Team Context
-A team object contains the following properties:
+
+ 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.
+
-| Property | Type | Description |
-| ----------------- | -------------- | --------------------------------------------------- |
-| `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"
-}
-```
+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.
## Team-Scoped API Tokens
@@ -156,26 +141,26 @@ Retrieve all documents belonging to the team:
```bash
# 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_xxxxxxxxxxxxxxxx"
# 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_xxxxxxxxxxxxxxxx"
````
```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',
headers: {
Authorization: TEAM_API_TOKEN,
},
});
-const { data, pagination } = await response.json();
-console.log(`Found ${pagination.totalItems} team documents`);
+const { data, count } = await response.json();
+console.log(`Found ${count} team documents`);
````
@@ -190,10 +175,11 @@ Templates created with a team token are shared across the team.
```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_xxxxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
+ "type": "TEMPLATE",
"title": "NDA Template",
"recipients": [
{
@@ -223,6 +209,7 @@ curl -X POST "https://app.documenso.com/api/v2/template/create" \
const form = new FormData();
const payload = {
+ type: 'TEMPLATE',
title: 'NDA Template',
recipients: [
{
@@ -249,7 +236,7 @@ form.append('files', fs.createReadStream('./nda-template.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',
headers: {
Authorization: TEAM_API_TOKEN,
@@ -257,8 +244,8 @@ const response = await fetch('https://app.documenso.com/api/v2/template/create',
body: form,
});
-const template = await response.json();
-console.log('Created team template:', template.id);
+const { id } = await response.json();
+console.log('Created team template envelope:', id);
````
@@ -268,14 +255,14 @@ console.log('Created team template:', template.id);
```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_xxxxxxxxxxxxxxxx"
````
```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',
headers: {
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;
// 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 },
});
const salesDocs = await salesResponse.json();
// 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 },
});
const legalDocs = await legalResponse.json();
-console.log(`Sales team: ${salesDocs.pagination.totalItems} pending`);
-console.log(`Legal team: ${legalDocs.pagination.totalItems} completed`);
+console.log(`Sales team: ${salesDocs.count} pending`);
+console.log(`Legal team: ${legalDocs.count} completed`);
```
## Error Responses
diff --git a/apps/docs/content/docs/developers/api/templates.mdx b/apps/docs/content/docs/developers/api/templates.mdx
index b3f52e146..2d3675100 100644
--- a/apps/docs/content/docs/developers/api/templates.mdx
+++ b/apps/docs/content/docs/developers/api/templates.mdx
@@ -13,7 +13,194 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
see the [OpenAPI Reference](https://openapi.documenso.com).
-## 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
+
+
+
+```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
+ }'
+```
+
+
+```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);
+```
+
+
+
+### 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
+
+
+ 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.
+
+
+## Legacy Template Object
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.
@@ -139,8 +326,8 @@ const response = await fetch(`${BASE_URL}/template`, {
},
});
-const { data, pagination } = await response.json();
-console.log(`Found ${pagination.totalItems} templates`);
+const { data, count } = await response.json();
+console.log(`Found ${count} templates`);
// Filter by type
const privateResponse = await fetch(
@@ -181,18 +368,16 @@ const privateTemplates = await privateResponse.json();
]
}
],
- "pagination": {
- "page": 1,
- "perPage": 10,
- "totalPages": 3,
- "totalItems": 25
- }
+ "count": 25,
+ "currentPage": 1,
+ "perPage": 10,
+ "totalPages": 3
}
```
---
-## Get Template
+## Get Template (Deprecated)
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.
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
-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
{
- "id": "envelope_xyz789",
- "type": "DOCUMENT",
+ "id": 789,
+ "envelopeId": "envelope_xyz789",
"status": "PENDING",
- "title": "Employment Contract",
"source": "TEMPLATE",
+ "title": "Employment Contract",
"externalId": "contract-2025-001",
"recipients": [
{
"id": 1,
+ "envelopeId": "envelope_xyz789",
+ "documentId": 789,
+ "templateId": null,
"email": "john.doe@example.com",
"name": "John Doe",
"role": "SIGNER",
"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:
@@ -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.
@@ -577,7 +787,7 @@ const response = await fetch(`${BASE_URL}/template/use`, {
---
-## Update Template
+## Update Template (Deprecated)
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.
@@ -695,7 +905,7 @@ console.log('New template ID:', duplicatedTemplate.id);
---
-## Delete Template
+## Delete Template (Deprecated)
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.
@@ -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.
@@ -913,7 +1123,7 @@ See the [OpenAPI Reference](https://openapi.documenso.com) for the full request
---
-## Template Types
+## Template Types (Legacy)
| 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.
@@ -996,16 +1206,29 @@ async function sendEmploymentContract(employeeData: {
subject: `Employment Contract for ${employeeData.name}`,
message: `Hi ${employeeData.name},\n\nPlease review and sign your employment contract.`,
},
- distributeDocument: true,
+ distributeDocument: false,
externalId: `emp-contract-${Date.now()}`,
}),
});
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 {
- documentId: document.id,
- signingUrl: document.recipients[0].signingUrl,
+ envelopeId: document.envelopeId,
+ signingUrl: distribution.recipients[0].signingUrl,
};
}
@@ -1018,7 +1241,7 @@ const result = await sendEmploymentContract({
startDate: '2025-03-01',
});
-console.log('Document created:', result.documentId);
+console.log('Document created:', result.envelopeId);
console.log('Signing URL:', result.signingUrl);
````
diff --git a/apps/docs/content/docs/developers/examples/common-workflows.mdx b/apps/docs/content/docs/developers/examples/common-workflows.mdx
index 5cac47f79..02f99506b 100644
--- a/apps/docs/content/docs/developers/examples/common-workflows.mdx
+++ b/apps/docs/content/docs/developers/examples/common-workflows.mdx
@@ -262,8 +262,11 @@ Use templates for repeatable document workflows. This example creates an employm
Map template fields by label and build a prefillFields array
- Call POST /template/use with recipients, prefill data, and{' '}
- distributeDocument: true
+ Call POST /template/use with recipients and prefill data
+
+
+ Distribute the returned envelope via POST /envelope/distribute and read its signing
+ links
@@ -297,7 +300,7 @@ type TemplateRecipient = {
async function sendEmploymentContract(
templateId: number,
employee: EmployeeData,
-): Promise<{ documentId: string; signingUrl: string }> {
+): Promise<{ documentId: number; signingUrl: string }> {
const templateResponse = await fetch(`${BASE_URL}/template/${templateId}`, {
headers: { Authorization: API_TOKEN },
});
@@ -374,7 +377,6 @@ async function sendEmploymentContract(
subject: `Your Employment Contract at ${employee.department}`,
message: `Hi ${employee.name},\n\nPlease review and sign your employment contract for the ${employee.position} position.\n\nStart date: ${employee.startDate}`,
},
- distributeDocument: true,
externalId: `emp-${Date.now()}-${employee.email.split('@')[0]}`,
}),
});
@@ -386,9 +388,25 @@ async function sendEmploymentContract(
const document = await createResponse.json();
+ const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
+ method: 'POST',
+ headers: {
+ Authorization: API_TOKEN,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ envelopeId: document.envelopeId }),
+ });
+
+ if (!distributeResponse.ok) {
+ const error = await distributeResponse.json();
+ throw new Error(`Failed to send document: ${error.message}`);
+ }
+
+ const distributeResult = await distributeResponse.json();
+
return {
documentId: document.id,
- signingUrl: document.recipients[0].signingUrl,
+ signingUrl: distributeResult.recipients[0].signingUrl,
};
}
@@ -453,12 +471,17 @@ RESPONSE=$(curl -s -X POST "${BASE_URL}/template/use" \
\"subject\": \"Your Employment Contract\",
\"message\": \"Please review and sign your employment contract.\"
},
- \"distributeDocument\": true,
\"externalId\": \"emp-$(date +%s)-alice\"
}")
-echo "Document created:"
-echo $RESPONSE | jq '{id, signingUrl: .recipients[0].signingUrl}'
+ENVELOPE_ID=$(echo $RESPONSE | jq -r '.envelopeId')
+DISTRIBUTE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/distribute" \
+ -H "Authorization: ${API_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d "{\"envelopeId\": \"${ENVELOPE_ID}\"}")
+
+echo "Document created: $(echo $RESPONSE | jq -r '.id')"
+echo "Signing URL: $(echo $DISTRIBUTE_RESPONSE | jq -r '.recipients[0].signingUrl')"
````
@@ -476,8 +499,10 @@ Send the same document to multiple recipients in parallel. Useful for policy ack
Fetch the template and get the signer recipient slot ID
- For each recipient, call POST /template/use with{' '}
- distributeDocument: true
+ For each recipient, call POST /template/use
+
+
+ Distribute each returned envelope via POST /envelope/distribute
Process in batches with a short delay to respect rate limits (e.g. 1000 requests/minute)
@@ -540,7 +565,6 @@ async function bulkSendFromTemplate(
recipients: [
{ id: signerSlot.id, email: recipient.email, name: recipient.name },
],
- distributeDocument: true,
externalId: `bulk-${Date.now()}-${recipient.email}`,
}),
});
@@ -551,10 +575,26 @@ async function bulkSendFromTemplate(
}
const document = await response.json();
+
+ const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
+ method: 'POST',
+ headers: {
+ Authorization: API_TOKEN,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ envelopeId: document.envelopeId }),
+ });
+
+ if (!distributeResponse.ok) {
+ const error = await distributeResponse.json();
+ throw new Error(error.message || 'Failed to distribute document');
+ }
+
+ const distributeResult = await distributeResponse.json();
return {
email: recipient.email,
- envelopeId: document.id,
- signingUrl: document.recipients[0].signingUrl,
+ envelopeId: document.envelopeId,
+ signingUrl: distributeResult.recipients[0].signingUrl,
};
}),
);
@@ -627,14 +667,24 @@ for RECIPIENT in "${RECIPIENTS[@]}"; do
\"email\": \"${EMAIL}\",
\"name\": \"${NAME}\"
}],
- \"distributeDocument\": true,
\"externalId\": \"bulk-$(date +%s)-${EMAIL}\"
}")
- if echo $RESPONSE | jq -e '.id' > /dev/null 2>&1; then
- echo " Success: $(echo $RESPONSE | jq -r '.id')"
+ if echo $RESPONSE | jq -e '.envelopeId' > /dev/null 2>&1; then
+ ENVELOPE_ID=$(echo $RESPONSE | jq -r '.envelopeId')
+ DISTRIBUTE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/distribute" \
+ -H "Authorization: ${API_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d "{\"envelopeId\": \"${ENVELOPE_ID}\"}")
+
+ if echo $DISTRIBUTE_RESPONSE | jq -e '.success' > /dev/null 2>&1; then
+ echo " Success: ${ENVELOPE_ID}"
+ echo " Signing URL: $(echo $DISTRIBUTE_RESPONSE | jq -r '.recipients[0].signingUrl')"
+ else
+ echo " Failed to distribute: $(echo $DISTRIBUTE_RESPONSE | jq -r '.message')"
+ fi
else
- echo " Failed: $(echo $RESPONSE | jq -r '.message')"
+ echo " Failed to create: $(echo $RESPONSE | jq -r '.message')"
fi
# Rate limiting delay
@@ -837,13 +887,15 @@ After a document is completed, download the signed PDF with all signatures embed
+The `version` query parameter accepts `original`, `pending`, or `signed`.
+
```typescript
const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';
-type DownloadVersion = 'signed' | 'original';
+type DownloadVersion = 'original' | 'pending' | 'signed';
async function downloadDocument(
envelopeId: string,
@@ -897,7 +949,7 @@ async function downloadAllCompletedDocuments(outputDir: string): Promise {
{ headers: { Authorization: API_TOKEN } },
);
- const { data, pagination } = await response.json();
+ const { data, count, currentPage, perPage, totalPages } = await response.json();
for (const envelope of data) {
try {
@@ -911,8 +963,8 @@ async function downloadAllCompletedDocuments(outputDir: string): Promise {
await new Promise((resolve) => setTimeout(resolve, 500));
}
- hasMore = page < pagination.totalPages;
- page++;
+ hasMore = currentPage < totalPages;
+ page = currentPage + 1;
}
}
diff --git a/apps/docs/content/docs/developers/getting-started/authentication.mdx b/apps/docs/content/docs/developers/getting-started/authentication.mdx
index 82d42bb1a..42d4311da 100644
--- a/apps/docs/content/docs/developers/getting-started/authentication.mdx
+++ b/apps/docs/content/docs/developers/getting-started/authentication.mdx
@@ -24,20 +24,18 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
{/* prettier-ignore */}
-### Open settings
+### Select a team
-- Log in to your Documenso account
-- Click your avatar in the top right corner
-- Select **Settings** from the dropdown menu
-
-
+Log in to Documenso and open the team that the integration should access. API tokens are scoped to a
+team.
-### Navigate to the API Tokens tab
+### Open API Tokens
-Go to **Settings** and open the **API Tokens** tab.
+Go to **Team Settings** → **API Tokens**, or open
+`/t/{teamUrl}/settings/tokens` after replacing `{teamUrl}` with your team's URL.

@@ -48,7 +46,7 @@ Go to **Settings** and open the **API Tokens** tab.
- Click **Create Token**
- Enter a descriptive name (e.g., `production-backend`, `zapier-integration`)
-- Select an expiration period: never expires, 7 days, 1 month, 3 months, 6 months, or 1 year
+- Select an expiration period: 7 days, 1 month, 3 months, 6 months, 12 months, or Never
- Click **Create Token**
@@ -75,21 +73,21 @@ Include the token in the `Authorization` header of your HTTP requests.
### cURL
```bash
-curl https://app.documenso.com/api/v2/document \
+curl https://app.documenso.com/api/v2/envelope \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
### JavaScript / TypeScript
```typescript
-const response = await fetch('https://app.documenso.com/api/v2/document', {
+const response = await fetch('https://app.documenso.com/api/v2/envelope', {
method: 'GET',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
});
-const documents = await response.json();
+const envelopes = await response.json();
```
### Using the TypeScript SDK
@@ -129,7 +127,7 @@ SDKs are available for [TypeScript](https://github.com/documenso/sdk-typescript)
## Token Security
-API tokens grant full access to your account. Follow these practices to keep them secure:
+API tokens grant full API access to the team they were created for. Follow these practices to keep them secure:
- **Never commit tokens to version control.** Use environment variables instead.
- **Use descriptive names.** Names like `zapier-prod` or `backend-staging` help you identify token usage.
@@ -155,12 +153,11 @@ const client = new Documenso({
## Token Scope
-API tokens have full access to your account, including:
+API tokens have full API access to the team they were created for, including:
- Creating, reading, updating, and deleting documents
- Managing recipients and fields
- Accessing templates
-- Managing team resources (if the token owner has team access)
There is currently no way to create tokens with limited scopes or permissions.
@@ -171,7 +168,7 @@ To revoke a token:
{/* prettier-ignore */}
- Go to **Settings** > **API Tokens**
+ Go to **Team Settings** → **API Tokens**
Find the token you want to revoke
@@ -194,7 +191,7 @@ Revoked tokens stop working immediately. Any integrations using that token will
Create a new token in settings.
- Ensure you're accessing resources owned by the token's account.
+ Ensure you're accessing resources owned by the token's team.
diff --git a/apps/docs/content/docs/developers/getting-started/first-api-call.mdx b/apps/docs/content/docs/developers/getting-started/first-api-call.mdx
index e87b85438..3f9083959 100644
--- a/apps/docs/content/docs/developers/getting-started/first-api-call.mdx
+++ b/apps/docs/content/docs/developers/getting-started/first-api-call.mdx
@@ -78,12 +78,10 @@ A successful response returns a list of your documents (envelopes):
"createdAt": "2025-01-15T10:30:00.000Z"
}
],
- "pagination": {
- "page": 1,
- "perPage": 10,
- "totalPages": 1,
- "totalItems": 1
- }
+ "count": 1,
+ "currentPage": 1,
+ "perPage": 10,
+ "totalPages": 1
}
````
@@ -228,9 +226,12 @@ After creating a document, it's in `DRAFT` status. To send it to recipients, use
```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 "Content-Type: application/json"
+ -H "Content-Type: application/json" \
+ -d '{
+ "envelopeId": "envelope_abc123"
+ }'
````
@@ -238,16 +239,14 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/envelope_abc123/distribu
```javascript
const envelopeId = 'envelope_abc123';
-const response = await fetch(
- `https://app.documenso.com/api/v2/envelope/${envelopeId}/distribute`,
- {
- method: 'POST',
- headers: {
- Authorization: 'YOUR_API_TOKEN',
- 'Content-Type': 'application/json',
- },
+const response = await fetch('https://app.documenso.com/api/v2/envelope/distribute', {
+ method: 'POST',
+ headers: {
+ Authorization: 'YOUR_API_TOKEN',
+ 'Content-Type': 'application/json',
},
-);
+ body: JSON.stringify({ envelopeId }),
+});
const data = await response.json();
console.log('Document sent:', data);
@@ -337,16 +336,14 @@ async function createAndSendDocument(pdfPath, recipientEmail, recipientName) {
console.log('Created envelope:', envelope.id);
// Step 2: Send the document for signing
- const distributeResponse = await fetch(
- `${BASE_URL}/envelope/${envelope.id}/distribute`,
- {
- method: 'POST',
- headers: {
- 'Authorization': API_TOKEN,
- 'Content-Type': 'application/json',
- },
- }
- );
+ const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': API_TOKEN,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ envelopeId: envelope.id }),
+ });
if (!distributeResponse.ok) {
const error = await distributeResponse.json();
@@ -422,9 +419,12 @@ echo "Created envelope: ${ENVELOPE_ID}"
# Step 2: Send the document for signing
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 "Content-Type: application/json"
+ -H "Content-Type: application/json" \
+ -d "{
+ \"envelopeId\": \"${ENVELOPE_ID}\"
+ }"
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 |
| `401` | Unauthorized - invalid or missing API token |
| `404` | Not found - resource doesn't exist |
-| `429` | Rate limited - wait 60 seconds and retry |
+| `429` | Rate limit or plan quota. If `Retry-After` is present, retry the request. |
| `500` | Server error - retry or contact support |
### Error Response Format
@@ -485,7 +485,9 @@ The API returns standard HTTP status codes and JSON error responses:
### 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 has a limit of 1000 requests per minute for each IP address, and your organisation can have a lower limit. Each response includes `X-RateLimit-Remaining` and `X-RateLimit-Reset` (an epoch timestamp in seconds). A windowed rate-limit `429` response includes `Retry-After`. Wait for that number of seconds before you send the request again. A quota `429` response does not include `Retry-After` because a wait cannot correct the quota error. If `Retry-After` is not present, do not send the request again automatically.
+
+Refer to [Error Handling Patterns](/docs/developers/examples/common-workflows#error-handling-patterns) for more retry information.
```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
@@ -493,8 +495,15 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
const response = await fetch(url, options);
if (response.status === 429) {
- console.log('Rate limited, waiting 60 seconds...');
- await new Promise((resolve) => setTimeout(resolve, 60000));
+ const retryAfter = response.headers.get('Retry-After');
+
+ if (!retryAfter) {
+ return response;
+ }
+
+ const retryAfterSeconds = Number.parseInt(retryAfter, 10);
+ console.log(`Rate limit. Wait ${retryAfterSeconds} seconds...`);
+ await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
continue;
}
diff --git a/apps/remix/app/components/general/app-command-menu.tsx b/apps/remix/app/components/general/app-command-menu.tsx
index 2e42b5c4e..7a53ff249 100644
--- a/apps/remix/app/components/general/app-command-menu.tsx
+++ b/apps/remix/app/components/general/app-command-menu.tsx
@@ -599,18 +599,10 @@ export const AppCommandMenu = ({ open, onOpenChange }: AppCommandMenuProps) => {
isVisibleCountCapped ? (
{formatChipCount(totalVisibleCount, isVisibleCountCapped)} results
) : (
-
+
)
) : (
-
+
)}
diff --git a/packages/lib/utils/render-custom-email-template.test.ts b/packages/lib/utils/render-custom-email-template.test.ts
index b1bd4d2c7..d790f60fd 100644
--- a/packages/lib/utils/render-custom-email-template.test.ts
+++ b/packages/lib/utils/render-custom-email-template.test.ts
@@ -8,15 +8,15 @@ describe('renderCustomEmailTemplate', () => {
});
it('replaces multiple variables separated by whitespace', () => {
- expect(
- renderCustomEmailTemplate('Hi {name}, sign at {url}', { name: 'Sam', url: 'https://x' }),
- ).toBe('Hi Sam, sign at https://x');
+ expect(renderCustomEmailTemplate('Hi {name}, sign at {url}', { name: 'Sam', url: 'https://x' })).toBe(
+ 'Hi Sam, sign at https://x',
+ );
});
it('replaces adjacent variables and variables separated by a non-whitespace character', () => {
- expect(
- renderCustomEmailTemplate('{day}/{month}/{year}', { day: '01', month: '02', year: '2026' }),
- ).toBe('01/02/2026');
+ expect(renderCustomEmailTemplate('{day}/{month}/{year}', { day: '01', month: '02', year: '2026' })).toBe(
+ '01/02/2026',
+ );
expect(
renderCustomEmailTemplate('{firstName}-{lastName}', {
diff --git a/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts
index 390caea84..735dd5ab3 100644
--- a/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts
+++ b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts
@@ -29,7 +29,17 @@ export const updateEnvelopeFieldsRoute = authenticatedProcedure
id: envelopeId,
},
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,
});