640 lines
18 KiB
Markdown
640 lines
18 KiB
Markdown
# API Reference
|
||
|
||
**Project**: Salesforce Composite Envelope Builder
|
||
**Version**: 1.2
|
||
**Date**: February 23, 2026 (updated March 13, 2026)
|
||
|
||
---
|
||
|
||
## 1. Docusign REST API Integration
|
||
|
||
### 1.1 Base Endpoint
|
||
|
||
**Production**:
|
||
```
|
||
https://na3.docusign.net/restapi/v2.1
|
||
```
|
||
|
||
**Sandbox**:
|
||
```
|
||
https://demo.docusign.net/restapi/v2.1
|
||
```
|
||
|
||
**Note**: Replace `na3` with your account's data center (na2, na3, eu1, etc.)
|
||
|
||
---
|
||
|
||
## 2. Create Composite Envelope
|
||
|
||
### 2.1 HTTP Request
|
||
|
||
**Method**: POST
|
||
**Endpoint**: `/accounts/{accountId}/envelopes`
|
||
**Content-Type**: `application/json`
|
||
**Authorization**: `Bearer {access_token}`
|
||
|
||
### 2.2 Request Headers
|
||
|
||
```http
|
||
POST /restapi/v2.1/accounts/12345678-abcd-1234-abcd-1234567890ab/envelopes HTTP/1.1
|
||
Host: na3.docusign.net
|
||
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjY4MTg...
|
||
Content-Type: application/json
|
||
Accept: application/json
|
||
```
|
||
|
||
### 2.3 Request Body (Composite Templates)
|
||
|
||
#### Minimum Example (2 templates)
|
||
|
||
```json
|
||
{
|
||
"status": "sent",
|
||
"emailSubject": "Please review and sign these forms",
|
||
"compositeTemplates": [
|
||
{
|
||
"compositeTemplateId": "1",
|
||
"serverTemplates": [
|
||
{
|
||
"sequence": "1",
|
||
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"compositeTemplateId": "2",
|
||
"serverTemplates": [
|
||
{
|
||
"sequence": "2",
|
||
"templateId": "b2c3d4e5-f6g7-8901-bcde-fg2345678901"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### Advanced Example (with merge fields)
|
||
|
||
```json
|
||
{
|
||
"status": "sent",
|
||
"emailSubject": "Please review and sign these forms",
|
||
"compositeTemplates": [
|
||
{
|
||
"compositeTemplateId": "1",
|
||
"serverTemplates": [
|
||
{
|
||
"sequence": "1",
|
||
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||
}
|
||
],
|
||
"inlineTemplates": [
|
||
{
|
||
"sequence": "1",
|
||
"customFields": {
|
||
"textCustomFields": [
|
||
{
|
||
"name": "SalesforceRecordId",
|
||
"value": "0018V00000ABC123"
|
||
},
|
||
{
|
||
"name": "CaseNumber",
|
||
"value": "00001234"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 2.4 Response
|
||
|
||
#### Success (201 Created)
|
||
|
||
```json
|
||
{
|
||
"envelopeId": "f9876543-21ab-cdef-0123-456789abcdef",
|
||
"uri": "/envelopes/f9876543-21ab-cdef-0123-456789abcdef",
|
||
"statusDateTime": "2026-02-23T12:34:56.789Z",
|
||
"status": "sent"
|
||
}
|
||
```
|
||
|
||
#### Error (400 Bad Request)
|
||
|
||
```json
|
||
{
|
||
"errorCode": "INVALID_REQUEST_PARAMETER",
|
||
"message": "The request contained at least one invalid parameter. The template with ID 'invalid-id' does not exist."
|
||
}
|
||
```
|
||
|
||
#### Error (401 Unauthorized)
|
||
|
||
```json
|
||
{
|
||
"errorCode": "USER_AUTHENTICATION_FAILED",
|
||
"message": "One or both of Username and Password are invalid."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Composite Template Structure
|
||
|
||
### 3.1 Key Concepts
|
||
|
||
**compositeTemplates** (array):
|
||
- Each element represents a server template to include in the envelope
|
||
- Order in array determines document order in envelope
|
||
|
||
**compositeTemplateId** (string):
|
||
- Unique identifier for this composite template within the request
|
||
- Can be any string (recommend using sequence numbers: "1", "2", "3")
|
||
|
||
**serverTemplates** (array):
|
||
- References pre-existing templates in your Docusign account
|
||
- Must include `templateId` (GUID from Docusign)
|
||
|
||
**sequence** (string):
|
||
- Determines document order within the envelope
|
||
- "1" = first document, "2" = second, etc.
|
||
- **Important**: Use strings, not integers
|
||
|
||
**inlineTemplates** (array, optional):
|
||
- Allows runtime override of template values
|
||
- Used for custom fields, recipient data, tabs
|
||
|
||
### 3.2 Recipient Merging
|
||
|
||
**Automatic Merge**:
|
||
When multiple templates have recipients with the same `roleName`, Docusign automatically merges them into a single recipient.
|
||
|
||
**Example**:
|
||
- Template A has recipient role: "Signer"
|
||
- Template B has recipient role: "Signer"
|
||
- Result: One recipient signs all documents
|
||
|
||
**Requirements for merge**:
|
||
- Exact match on `roleName`
|
||
- Same recipient `routingOrder` (signing order)
|
||
- Merge happens automatically, no additional configuration needed
|
||
|
||
---
|
||
|
||
## 4. Authentication
|
||
|
||
### 4.1 JWT (JSON Web Token)
|
||
|
||
**Preferred method for server-to-server integration**
|
||
|
||
#### Step 1: Generate JWT
|
||
|
||
```apex
|
||
// Apex pseudocode
|
||
String jwt = generateJWT(
|
||
integrationKey, // From Docusign Apps & Keys
|
||
userId, // Docusign user GUID
|
||
rsaPrivateKey, // RSA private key (PEM format)
|
||
'https://account-d.docusign.com/oauth/auth', // Sandbox
|
||
3600 // Token expiry (1 hour)
|
||
);
|
||
```
|
||
|
||
#### Step 2: Exchange JWT for Access Token
|
||
|
||
**Request**:
|
||
```http
|
||
POST /oauth/token HTTP/1.1
|
||
Host: account-d.docusign.com
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={jwt}
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"access_token": "eyJ0eXAiOiJNVCIsImFsZyI...",
|
||
"token_type": "Bearer",
|
||
"expires_in": 3600
|
||
}
|
||
```
|
||
|
||
#### Step 3: Use Access Token
|
||
|
||
```http
|
||
Authorization: Bearer eyJ0eXAiOiJNVCIsImFsZyI...
|
||
```
|
||
|
||
### 4.2 OAuth2 Authorization Code
|
||
|
||
**User-based authentication** (not recommended for this use case)
|
||
|
||
See [Docusign OAuth2 documentation](https://developers.docusign.com/platform/auth/authcode/) for details.
|
||
|
||
---
|
||
|
||
## 5. Error Codes
|
||
|
||
### 5.1 Common Error Codes
|
||
|
||
| Error Code | HTTP | Meaning | Resolution |
|
||
|------------|------|---------|------------|
|
||
| `INVALID_REQUEST_PARAMETER` | 400 | Invalid parameter in request | Check JSON structure, template IDs |
|
||
| `USER_AUTHENTICATION_FAILED` | 401 | Invalid or expired access token | Refresh access token |
|
||
| `USER_LACKS_PERMISSIONS` | 403 | User doesn't have permission | Check Docusign user permissions |
|
||
| `RESOURCE_NOT_FOUND` | 404 | Template ID not found | Verify template exists in account |
|
||
| `DUPLICATE_RESOURCE` | 409 | Duplicate request | Check for duplicate envelope |
|
||
| `ONESIGNAL_GENERIC_ERROR` | 500 | Docusign server error | Retry after delay |
|
||
|
||
### 5.2 Rate Limits
|
||
|
||
**Hourly limit**: Varies by plan (1,000 - 10,000+ API calls/hour)
|
||
|
||
**Response header when nearing limit**:
|
||
```http
|
||
X-RateLimit-Limit: 1000
|
||
X-RateLimit-Remaining: 50
|
||
X-RateLimit-Reset: 1614358800
|
||
```
|
||
|
||
**Error when limit exceeded**:
|
||
```json
|
||
{
|
||
"errorCode": "HOURLY_APIINVOCATION_LIMIT_EXCEEDED",
|
||
"message": "The maximum number of hourly API invocations has been exceeded."
|
||
}
|
||
```
|
||
|
||
**Mitigation**:
|
||
- Implement exponential backoff
|
||
- Cache access tokens (don't generate new token for each request)
|
||
- Batch operations when possible
|
||
|
||
---
|
||
|
||
## 6. Custom Fields
|
||
|
||
### 6.1 Text Custom Fields
|
||
|
||
**Use case**: Store Salesforce record ID in envelope
|
||
|
||
```json
|
||
{
|
||
"compositeTemplates": [
|
||
{
|
||
"inlineTemplates": [
|
||
{
|
||
"customFields": {
|
||
"textCustomFields": [
|
||
{
|
||
"name": "SalesforceRecordId",
|
||
"value": "0018V00000ABC123",
|
||
"show": "false",
|
||
"required": "false"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 6.2 List Custom Fields
|
||
|
||
```json
|
||
{
|
||
"customFields": {
|
||
"listCustomFields": [
|
||
{
|
||
"name": "FormLanguage",
|
||
"value": "English",
|
||
"listItems": ["English", "Spanish"]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Webhook Configuration
|
||
|
||
### 7.1 Connect Webhook (for document retrieval)
|
||
|
||
**Not required for Phase 1**, but useful for future automation.
|
||
|
||
**Endpoint**: Configure in Docusign Admin → Connect → Add Configuration
|
||
|
||
**Webhook payload** (when envelope completes):
|
||
```json
|
||
{
|
||
"event": "envelope-completed",
|
||
"apiVersion": "v2.1",
|
||
"uri": "/restapi/v2.1/accounts/123/envelopes/abc",
|
||
"envelopeId": "f9876543-21ab-cdef-0123-456789abcdef",
|
||
"envelopeSummary": {
|
||
"status": "completed",
|
||
"emailSubject": "Please review and sign",
|
||
"completedDateTime": "2026-02-23T14:30:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Template Management APIs
|
||
|
||
### 8.1 List Templates
|
||
|
||
**Use case**: Retrieve template list for Flow dropdown
|
||
|
||
**Request**:
|
||
```http
|
||
GET /restapi/v2.1/accounts/{accountId}/templates?count=100&order=name&order_by=asc
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"resultSetSize": "14",
|
||
"totalSetSize": "14",
|
||
"envelopeTemplates": [
|
||
{
|
||
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||
"name": "Form A - English",
|
||
"shared": "true",
|
||
"description": "English version of Form A"
|
||
},
|
||
{
|
||
"templateId": "b2c3d4e5-f6g7-8901-bcde-fg2345678901",
|
||
"name": "Form B - English",
|
||
"shared": "true",
|
||
"description": "English version of Form B"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 8.2 Get Template Details
|
||
|
||
**Use case**: Retrieve template metadata (recipients, tabs)
|
||
|
||
**Request**:
|
||
```http
|
||
GET /restapi/v2.1/accounts/{accountId}/templates/{templateId}
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||
"name": "Form A - English",
|
||
"recipients": {
|
||
"signers": [
|
||
{
|
||
"roleName": "Signer 1",
|
||
"recipientId": "1",
|
||
"routingOrder": "1"
|
||
},
|
||
{
|
||
"roleName": "Signer 2",
|
||
"recipientId": "2",
|
||
"routingOrder": "2"
|
||
}
|
||
]
|
||
},
|
||
"documents": [
|
||
{
|
||
"documentId": "1",
|
||
"name": "Form_A.pdf",
|
||
"pages": "2"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Apex HTTP Callout Example
|
||
|
||
### 9.1 Complete Callout
|
||
|
||
```apex
|
||
public static String createCompositeEnvelope(String envelopeJSON, String accessToken, String accountId) {
|
||
HttpRequest req = new HttpRequest();
|
||
req.setEndpoint('callout:DocusignAPI/accounts/' + accountId + '/envelopes');
|
||
req.setMethod('POST');
|
||
req.setHeader('Authorization', 'Bearer ' + accessToken);
|
||
req.setHeader('Content-Type', 'application/json');
|
||
req.setHeader('Accept', 'application/json');
|
||
req.setBody(envelopeJSON);
|
||
req.setTimeout(120000); // 120 seconds
|
||
|
||
Http http = new Http();
|
||
HttpResponse res = http.send(req);
|
||
|
||
if (res.getStatusCode() == 201) {
|
||
Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
|
||
return (String) responseMap.get('envelopeId');
|
||
} else {
|
||
throw new CalloutException('Docusign API error [' + res.getStatusCode() + ']: ' + res.getBody());
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 Named Credential Configuration
|
||
|
||
**Setup → Named Credentials → New Named Credential**
|
||
|
||
**Settings**:
|
||
- **Label**: Docusign API
|
||
- **Name**: DocusignAPI
|
||
- **URL**: `https://na3.docusign.net/restapi/v2.1`
|
||
- **Identity Type**: Named Principal
|
||
- **Authentication Protocol**: OAuth 2.0
|
||
- **Scope**: `signature impersonation`
|
||
- **Token Endpoint**: `https://account-d.docusign.com/oauth/token` (sandbox)
|
||
- **JWT Token Exchange**: Enabled
|
||
|
||
**Usage in Apex**:
|
||
```apex
|
||
req.setEndpoint('callout:DocusignAPI/accounts/' + accountId + '/envelopes');
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Testing with Postman
|
||
|
||
### 10.1 Import Docusign Collection
|
||
|
||
Docusign provides an official Postman collection:
|
||
https://github.com/docusign/postman-collections
|
||
|
||
### 10.2 Create Composite Envelope Test
|
||
|
||
**Request**:
|
||
```
|
||
POST {{baseUrl}}/accounts/{{accountId}}/envelopes
|
||
Headers:
|
||
Authorization: Bearer {{accessToken}}
|
||
Content-Type: application/json
|
||
Body:
|
||
{
|
||
"status": "sent",
|
||
"emailSubject": "Test Composite Envelope",
|
||
"compositeTemplates": [
|
||
{
|
||
"compositeTemplateId": "1",
|
||
"serverTemplates": [
|
||
{
|
||
"sequence": "1",
|
||
"templateId": "{{templateId1}}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"compositeTemplateId": "2",
|
||
"serverTemplates": [
|
||
{
|
||
"sequence": "2",
|
||
"templateId": "{{templateId2}}"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 11. SMS Delivery via dfsle Apex Toolkit (v1.2)
|
||
|
||
### 11.1 Overview
|
||
|
||
Rather than calling the Docusign REST API directly for SMS delivery, this project uses the native **dfsle Apex Toolkit** method `dfsle.Recipient.withSmsDelivery()`, which is part of the Docusign for Salesforce managed package already installed in the org.
|
||
|
||
This approach requires no additional REST endpoints, no extra authentication, and no HTTP callouts beyond what the toolkit already handles.
|
||
|
||
### 11.2 Salesforce Data Setup Prerequisite
|
||
|
||
Before SMS delivery can work, the Client Case record must be set up correctly:
|
||
|
||
| Field | Requirement |
|
||
|-------|-------------|
|
||
| `Docusign_Recipient_1__c` | **Must be populated** — set to a Contact record |
|
||
| Contact `Name` | **Must be populated** — used as the recipient's display name in Docusign |
|
||
| Contact `Email` | **Must be blank** — the flow checks this field to route through the SMS path |
|
||
|
||
> ⚠️ **Important**: If `Docusign_Recipient_1__c` is null (no Contact linked at all), the Apex code skips that recipient entirely and Docusign Recipient #1 will be missing from the envelope. The Contact must exist — only the email needs to be absent.
|
||
|
||
### 11.3 Placeholder Email Constant
|
||
|
||
The Docusign API requires an `email` field on every recipient even when SMS delivery is configured. When the recipient contact has no email address, the Apex layer substitutes a placeholder automatically — **no actual email is sent to this address**.
|
||
|
||
> 🔧 **Configuration**: The placeholder address is defined as a single constant in `DocusignCompositeEnvelopeBuilder.cls`. **This is the only place that needs to be updated if the placeholder address ever changes.**
|
||
|
||
```apex
|
||
// ============================================================
|
||
// SMS DELIVERY: Placeholder email used when the primary recipient
|
||
// (Docusign Recipient #1) has no email address and SMS delivery is
|
||
// requested via recipientSmsPhone. Docusign requires an email on
|
||
// every recipient even when dfsle.Recipient.withSmsDelivery() is used.
|
||
// ============================================================
|
||
@TestVisible
|
||
private static final String SMS_PLACEHOLDER_EMAIL = 'placeholder_email@docusign.com';
|
||
```
|
||
|
||
**Method signature**:
|
||
```apex
|
||
dfsle.Recipient withSmsDelivery(String phone)
|
||
```
|
||
|
||
**Parameters**:
|
||
| Parameter | Type | Description |
|
||
|-----------|------|-------------|
|
||
| `phone` | `String` | Mobile phone number in E.164 format (e.g. `+15551234567`). International numbers must include the country code. |
|
||
|
||
**Returns**: A new `dfsle.Recipient` instance with SMS delivery configured (`deliverBySms = true`).
|
||
|
||
**Important notes**:
|
||
- This method sets the delivery method for the signing invitation; it is **not** a 2FA/authentication method.
|
||
- The Docusign API still requires an `email` field on every recipient even when SMS delivery is configured. When the recipient contact has no email address, a placeholder (`placeholder_email@docusign.com`) is substituted automatically by the Apex layer. No actual email is sent to this address.
|
||
- Only applied to **Docusign Recipient #1**. The Service Coordinator always uses email delivery.
|
||
|
||
### 11.4 Invocable Action Parameter
|
||
|
||
The `recipientSmsPhone` input parameter on the `Send_Composite_Envelope` Apex action activates SMS delivery:
|
||
|
||
| Parameter | Type | Required | Description |
|
||
|-----------|------|----------|-------------|
|
||
| `recipientSmsPhone` | `String` | No | Mobile phone number for SMS delivery. When blank (or not provided), the envelope is sent normally by email. E.164 format preferred: `+15551234567`. |
|
||
|
||
### 11.5 Apex Usage Pattern
|
||
|
||
```apex
|
||
// Resolve email from Contact record
|
||
String recipientEmail = contact.Email;
|
||
|
||
// Substitute placeholder when email is blank and SMS phone is provided.
|
||
// SMS_PLACEHOLDER_EMAIL is a constant in DocusignCompositeEnvelopeBuilder.cls —
|
||
// update the constant there if the placeholder address ever needs to change.
|
||
if (String.isBlank(recipientEmail) && String.isNotBlank(smsPhone)) {
|
||
recipientEmail = SMS_PLACEHOLDER_EMAIL;
|
||
}
|
||
|
||
// Build recipient using dfsle toolkit
|
||
dfsle.Recipient recipient = dfsle.Recipient.fromSource(
|
||
contact.Name,
|
||
recipientEmail,
|
||
null, // phone param (not used here)
|
||
'Docusign Recipient #1', // role name — must match template exactly
|
||
new dfsle.Entity(caseRecordId) // source record for merge fields
|
||
);
|
||
|
||
// Enable SMS delivery when phone is provided
|
||
if (String.isNotBlank(smsPhone)) {
|
||
recipient = recipient.withSmsDelivery(smsPhone);
|
||
}
|
||
```
|
||
|
||
### 11.6 Flow V4 Integration
|
||
|
||
In `Docusign_Envelope_Templates_V4`, the flow checks the recipient contact's email before presenting the send screen:
|
||
|
||
1. **`Get_Recipient_Contact`** — queries the Contact linked to `Docusign_Recipient_1__c`
|
||
2. **`Is_Recipient_Email_Blank`** — if email is null or empty, routes to the phone collection screen; otherwise proceeds normally
|
||
3. **`SMS_Phone_Screen`** — collects the mobile number (required text field); stores result in the `recipientSmsPhone` flow variable
|
||
4. **`Send_Composite_Envelope`** action — receives `recipientSmsPhone` as an input; when non-blank the Apex layer applies `withSmsDelivery()`
|
||
|
||
---
|
||
|
||
## 12. Reference Links
|
||
|
||
- [Docusign REST API Reference](https://developers.docusign.com/docs/esign-rest-api/reference/)
|
||
- [Composite Templates Guide](https://developers.docusign.com/docs/esign-rest-api/how-to/request-signature-composite-template/)
|
||
- [JWT Authentication](https://developers.docusign.com/platform/auth/jwt/)
|
||
- [Salesforce Named Credentials](https://help.salesforce.com/s/articleView?id=sf.named_credentials_about.htm)
|
||
- [Salesforce HTTP Callouts](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_http_httprequest.htm)
|
||
|
||
---
|
||
|
||
**Document Version**: 1.2
|
||
**Last Updated**: March 15, 2026
|
||
**Change Log**:
|
||
|
||
| Version | Date | Summary |
|
||
|---------|------|---------|
|
||
| 1.0 | 2026-02-23 | Initial release |
|
||
| 1.1 | 2026-03-11 | No changes (version aligned with design/requirements) |
|
||
| 1.2 | 2026-03-13 | Added section 11 — SMS delivery via `dfsle.Recipient.withSmsDelivery()`; documented `recipientSmsPhone` parameter; renumbered Reference Links to section 12 |
|
||
| 1.2 | 2026-03-15 | Added 11.2 Contact setup prerequisite; added 11.3 placeholder email constant callout; renumbered 11.3–11.5 to 11.4–11.6; updated code example to reference `SMS_PLACEHOLDER_EMAIL` constant |
|
||
|