Compare commits

..
2 changed files with 67 additions and 35 deletions
@@ -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';
<EnvelopeWarning />
<Callout type="warn">
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).
@@ -62,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 |
@@ -140,6 +142,8 @@ POST /envelope/field/create-many
| `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
<Tabs items={['curl', 'TypeScript']}>
@@ -335,6 +339,8 @@ POST /envelope/field/update-many
| `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
<Tabs items={['curl', 'TypeScript']}>
@@ -551,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
@@ -78,10 +78,12 @@ A successful response returns a list of your documents (envelopes):
"createdAt": "2025-01-15T10:30:00.000Z"
}
],
"count": 1,
"currentPage": 1,
"perPage": 10,
"totalPages": 1
"pagination": {
"page": 1,
"perPage": 10,
"totalPages": 1,
"totalItems": 1
}
}
````
@@ -226,12 +228,9 @@ After creating a document, it's in `DRAFT` status. To send it to recipients, use
<Tabs items={['curl', 'JavaScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
curl -X POST "https://app.documenso.com/api/v2/envelope/envelope_abc123/distribute" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123"
}'
-H "Content-Type: application/json"
````
</Tab>
@@ -239,14 +238,16 @@ curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
```javascript
const envelopeId = 'envelope_abc123';
const response = await fetch('https://app.documenso.com/api/v2/envelope/distribute', {
method: 'POST',
headers: {
Authorization: 'YOUR_API_TOKEN',
'Content-Type': 'application/json',
const response = await fetch(
`https://app.documenso.com/api/v2/envelope/${envelopeId}/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);
@@ -336,14 +337,16 @@ 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/distribute`, {
method: 'POST',
headers: {
'Authorization': API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ envelopeId: envelope.id }),
});
const distributeResponse = await fetch(
`${BASE_URL}/envelope/${envelope.id}/distribute`,
{
method: 'POST',
headers: {
'Authorization': API_TOKEN,
'Content-Type': 'application/json',
},
}
);
if (!distributeResponse.ok) {
const error = await distributeResponse.json();
@@ -419,12 +422,9 @@ echo "Created envelope: ${ENVELOPE_ID}"
# Step 2: Send the document for signing
echo "Sending document..."
curl -s -X POST "${BASE_URL}/envelope/distribute" \
curl -s -X POST "${BASE_URL}/envelope/${ENVELOPE_ID}/distribute" \
-H "Authorization: ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"envelopeId\": \"${ENVELOPE_ID}\"
}"
-H "Content-Type: application/json"
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 for the duration in the `Retry-After` header |
| `429` | Rate limited - wait 60 seconds and retry |
| `500` | Server error - retry or contact support |
### Error Response Format
@@ -485,7 +485,7 @@ 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. 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.
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:
```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
@@ -493,9 +493,8 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfterSeconds = Number.parseInt(response.headers.get('Retry-After') ?? '1', 10);
console.log(`Rate limited, waiting ${retryAfterSeconds} seconds...`);
await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
console.log('Rate limited, waiting 60 seconds...');
await new Promise((resolve) => setTimeout(resolve, 60000));
continue;
}