Create documents
Creating a document in Signer involves two steps: uploading the file and creating the document by defining the participant flow. From there, Signer automatically notifies the participants in the defined order.
Step by step
Each step shows the REST call and the same call through the client libraries for C#, Java, Node.js and PHP. The code comes from the SignerSamples scenarios, trimmed to the call. Create the client once, with the URL of your Signer instance and the API key:
- C#
- Java
- Node.js
- PHP
using Lacuna.Signer.Client;
// Sandbox URL; in production, https://www.dropsigner.com or your on-premises instance
var signerClient = new SignerClient("https://signer-lac.azurewebsites.net", "your-application|0000000000000000000000000000000000000000000000000000000000000000");
import com.lacunasoftware.signer.javaclient.SignerClient;
// Sandbox URL; in production, https://www.dropsigner.com or your on-premises instance
// (this constructor throws URISyntaxException)
SignerClient signerClient = new SignerClient("https://signer-lac.azurewebsites.net", "your-application|0000000000000000000000000000000000000000000000000000000000000000");
import { Configuration, DocumentsApi, UploadApi } from "signer-node-client";
const config = new Configuration({
apiKey: "your-application|0000000000000000000000000000000000000000000000000000000000000000",
// Sandbox URL; in production, https://www.dropsigner.com or your on-premises instance
basePath: "https://signer-lac.azurewebsites.net",
});
const uploadApi = new UploadApi(config);
const documentsApi = new DocumentsApi(config);
use Lacuna\Signer\PhpClient\SignerClient;
// Sandbox URL; in production, https://www.dropsigner.com or your on-premises instance
$signerClient = new SignerClient("https://signer-lac.azurewebsites.net/", "your-application|0000000000000000000000000000000000000000000000000000000000000000");
The Node.js client is generated from the API: it has one class per API group (UploadApi, DocumentsApi) and one method per endpoint, named after the route, such as apiDocumentsPost for POST /api/documents. Every method returns the axios response, with the body in data.
1. Upload the file
Upload the file that will be signed. There are two upload APIs:
- The simplified Upload API (POST /api/uploads/bytes) takes a JSON request with the file bytes in Base64, in the
bytesfield. It is the simplest option, the one Getting started uses. - The Upload API (POST /api/uploads) takes the file in a multipart/form-data request. Prefer it for large files, since it avoids the overhead of transporting the file in Base64.
Both return an upload ID that identifies that file:
{
...
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
With a client library, uploadFile in Java and PHP sends the file to POST /api/uploads (multipart); in Node.js, apiUploadsBytesPost sends it to POST /api/uploads/bytes (Base64). The C#, Java and PHP methods return an upload model that the next step passes on whole; in Node.js, keep data.id.
- C#
- Java
- Node.js
- PHP
var file = File.ReadAllBytes("Contract.pdf");
var uploadModel = await signerClient.UploadFileAsync("Contract.pdf", file, "application/pdf");
import com.lacunasoftware.signer.javaclient.models.UploadModel;
byte[] content = Files.readAllBytes(Paths.get("Contract.pdf"));
UploadModel uploadModel = signerClient.uploadFile("Contract.pdf", content, "application/pdf");
import { getBase64 } from "signer-node-client";
// getBase64 reads the file and encodes it in Base64
const upload = await uploadApi.apiUploadsBytesPost({ bytes: getBase64("Contract.pdf") });
const uploadId = upload.data.id;
use Lacuna\Signer\PhpClient\Models\UploadModel;
$file = fopen("Contract.pdf", "r");
$uploadModel = new UploadModel($signerClient->uploadFile("Contract.pdf", $file, "application/pdf"));
// Guzzle closes the stream once it has sent it, and fclose() on a closed stream throws on PHP 8.
if (is_resource($file)) {
fclose($file);
}
2. Create the document
Create a document from the uploaded file using the Document Creation API. In this call, you assemble the document flow, that is, define the participants and in what order they should take their actions:
POST /api/documents
{
"files": [
{
"displayName": "Integration Contract", //Name that will be given to the created document
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //File's upload ID
"name": "Contract.pdf", //Name of the original file
"contentType": "application/pdf" //Original file mime type
}
],
"flowActions": [
{
"type": "Signer", //Type of participant, in this scenario, signer
"step": 1, //Signing order
"user": {
"name": "John Wick", //Participant's name
"identifier": "81976153069", //Participant's identifier number
"email": "john.wick@mailinator.com", //Participant's email
},
"allowElectronicSignature": true //Allows electronic signature
}
]
}
The response returns the ID of the created document associated with the upload ID, so you know exactly which document matches which upload:
[
{
"uploadId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"documentId": "b12cb1b2-5d6e-40b2-a050-097d068c4c11"
}
]
The same request with a client library. The create call returns the list of uploadId and documentId pairs; with one file, take the first.
- C#
- Java
- Node.js
- PHP
using Lacuna.Signer.Api;
using Lacuna.Signer.Api.Documents;
using Lacuna.Signer.Api.FlowActions;
using Lacuna.Signer.Api.Users;
// The name the document will have in Signer
var fileUploadModel = new FileUploadModel(uploadModel) { DisplayName = "Integration Contract" };
// One FlowActionCreateModel per action (signature or approval) of the flow
var flowAction = new FlowActionCreateModel()
{
Type = FlowActionType.Signer,
Step = 1,
User = new ParticipantUserModel()
{
Name = "John Wick",
Identifier = "81976153069",
Email = "john.wick@mailinator.com"
},
AllowElectronicSignature = true
};
var documentRequest = new CreateDocumentRequest()
{
Files = new List<FileUploadModel>() { fileUploadModel },
FlowActions = new List<FlowActionCreateModel>() { flowAction }
};
var result = (await signerClient.CreateDocumentAsync(documentRequest)).First();
// result.DocumentId, result.UploadId
import com.lacunasoftware.signer.FlowActionType;
import com.lacunasoftware.signer.documents.CreateDocumentRequest;
import com.lacunasoftware.signer.documents.CreateDocumentResult;
import com.lacunasoftware.signer.flowactions.FlowActionCreateModel;
import com.lacunasoftware.signer.javaclient.builders.FileUploadModelBuilder;
import com.lacunasoftware.signer.users.ParticipantUserModel;
// The name the document will have in Signer
FileUploadModelBuilder fileUploadModelBuilder = new FileUploadModelBuilder(uploadModel);
fileUploadModelBuilder.setDisplayName("Integration Contract");
ParticipantUserModel user = new ParticipantUserModel();
user.setName("John Wick");
user.setIdentifier("81976153069");
user.setEmail("john.wick@mailinator.com");
// One FlowActionCreateModel per action (signature or approval) of the flow
FlowActionCreateModel flowAction = new FlowActionCreateModel();
flowAction.setType(FlowActionType.SIGNER);
flowAction.setStep(1);
flowAction.setUser(user);
flowAction.setAllowElectronicSignature(true);
CreateDocumentRequest documentRequest = new CreateDocumentRequest();
documentRequest.addFilesItem(fileUploadModelBuilder.toModel());
documentRequest.addFlowActionsItem(flowAction);
CreateDocumentResult result = signerClient.createDocument(documentRequest).get(0);
// result.getDocumentId(), result.getUploadId()
import {
DocumentsCreateDocumentRequest,
FileUploadModel,
FlowActionsFlowActionCreateModel,
FlowActionType,
} from "signer-node-client";
const fileUploadModel: FileUploadModel = {
id: uploadId,
name: "Contract.pdf",
contentType: "application/pdf",
displayName: "Integration Contract", // the name the document will have in Signer
};
// One FlowActionsFlowActionCreateModel per action (signature or approval) of the flow
const flowAction: FlowActionsFlowActionCreateModel = {
type: FlowActionType.Signer,
step: 1,
user: { name: "John Wick", identifier: "81976153069", email: "john.wick@mailinator.com" },
allowElectronicSignature: true,
};
const documentRequest: DocumentsCreateDocumentRequest = {
files: [fileUploadModel],
flowActions: [flowAction],
};
const result = (await documentsApi.apiDocumentsPost(documentRequest)).data[0];
// result.documentId, result.uploadId
use Lacuna\Signer\Model\DocumentsCreateDocumentRequest;
use Lacuna\Signer\Model\FlowActionsFlowActionCreateModel;
use Lacuna\Signer\Model\FlowActionType;
use Lacuna\Signer\Model\UsersParticipantUserModel;
use Lacuna\Signer\PhpClient\Builders\FileUploadBuilder;
// The name the document will have in Signer
$fileUploadModelBuilder = new FileUploadBuilder($uploadModel);
$fileUploadModelBuilder->setDisplayName("Integration Contract");
$user = new UsersParticipantUserModel();
$user->setName("John Wick");
$user->setIdentifier("81976153069");
$user->setEmail("john.wick@mailinator.com");
// One FlowActionsFlowActionCreateModel per action (signature or approval) of the flow
$flowAction = new FlowActionsFlowActionCreateModel();
$flowAction->setType(FlowActionType::SIGNER);
$flowAction->setStep(1);
$flowAction->setUser($user);
$flowAction->setAllowElectronicSignature(true);
$documentRequest = new DocumentsCreateDocumentRequest();
$documentRequest->setFiles(array($fileUploadModelBuilder->toModel()));
$documentRequest->setFlowActions(array($flowAction));
$result = $signerClient->createDocument($documentRequest)[0];
// $result->getDocumentId(), $result->getUploadId()
You can create more than one document in the same call, adding as many files as needed. In this case, all documents will share the same flow.
When the document is created, Signer automatically notifies the participants in the specified order. Each participant receives an email with a link that lets them sign or approve the document without having to authenticate.
The request also accepts options that apply to all its documents, such as the timestamp and access validation. See Document options.
The flow (flowActions)
Each flowActions item represents a participant's action. The main fields are:
| Field | Description |
|---|---|
type | Action type (see the table below) |
step | Order of the action in the flow. Actions with the same step happen in parallel; different steps run in sequence |
user | Participant data (name, identifier, email). See The participant (user) |
allowElectronicSignature | Allows the participant to sign electronically (without a digital certificate) |
The available action types (type) are:
type | Description |
|---|---|
Signer | Participant who signs the document |
Approver | Participant who approves the document (without signing) |
SignRule | Signing rule: a group in which any member can sign |
ApproverRule | Approval rule: a group in which any member can approve |
In the client libraries these values are the FlowActionType enum. The FlowActionType of the .NET client (in Lacuna.Signer.Api 1.67.0, which Lacuna.Signer.Client 1.24.0 installs) and of the PHP client 1.4.10 has no ApproverRule; the Java and Node.js ones have all four.
Examples on GitHub, by scenario:
| Scenario | Examples |
|---|---|
| One signer | C# · Java · PHP · Node.js |
| Two or more signers, with order | C# · Java · PHP · Node.js |
| Two or more signers, without order | C# · Java · PHP · Node.js |
| With approver | C# · Java · PHP · Node.js |
| With attachment | C# · Java · PHP · Node.js |
| With description | C# · Java · PHP · Node.js |
For the participant fields and the identifier types, see The participant (user). For the other options of each flowActions item, such as initials and the electronic signature checks, see Participant options.
Signing in your own application
If you want to sign the document inside your own application, use the Embedded Signature option.
Follow the same steps from the previous section, but, when sending the document, it is recommended to add the parameter disablePendingActionNotifications set to true. This way, notifications will not be sent to participants.
After the creation of the document, use the document ID to get the signing URL using the Action URL API:
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/action-url
{
//the information must be sent to identify the participant
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com"
}
If the signer has allowed electronic signature (parameter allowElectronicSignature = true), it is possible to require an e-mail confirmation through a code asked during signing. To do so, set requireEmailAuthentication to true:
POST /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/action-url
{
//the information must be sent to identify the participant
"identifier": "81976153069",
"emailAddress": "john.wick@mailinator.com",
//if the action is an electronic signature and this parameter is set to true, requires e-mail authentication with code in order to complete the signature
"requireEmailAuthentication": true
}
The response presents two URLs:
{
//URL to redirect the user to the Signer signing page
"url": "https://...",
//URL to use the signing Widget
"embedUrl": "https://..."
}
Use the embedUrl with the Signing Widget to display Signer's signing page inside your application. The section Embedded signature describes how to use the Widget.
With a client library, set disablePendingActionNotifications on the request of step 2 and, once the document is created, ask for the participant's URLs:
- C#
- Java
- Node.js
- PHP
// Before CreateDocumentAsync: don't e-mail the participants
documentRequest.DisablePendingActionNotifications = true;
var result = (await signerClient.CreateDocumentAsync(documentRequest)).First();
var actionUrlRequest = new ActionUrlRequest()
{
Identifier = "81976153069",
EmailAddress = "john.wick@mailinator.com"
};
var actionUrlResponse = await signerClient.GetActionUrlAsync(result.DocumentId, actionUrlRequest);
// actionUrlResponse.Url: redirect the user to Signer
// actionUrlResponse.EmbedUrl: pass it to the widget
import com.lacunasoftware.signer.documents.ActionUrlRequest;
import com.lacunasoftware.signer.documents.ActionUrlResponse;
// Before createDocument: don't e-mail the participants
documentRequest.setDisablePendingActionNotifications(true);
CreateDocumentResult result = signerClient.createDocument(documentRequest).get(0);
ActionUrlRequest actionUrlRequest = new ActionUrlRequest();
actionUrlRequest.setIdentifier("81976153069");
actionUrlRequest.setEmailAddress("john.wick@mailinator.com");
ActionUrlResponse actionUrlResponse = signerClient.getActionUrl(result.getDocumentId(), actionUrlRequest);
// actionUrlResponse.getUrl(): redirect the user to Signer
// actionUrlResponse.getEmbedUrl(): pass it to the widget
import { DocumentsActionUrlRequest } from "signer-node-client";
// Don't e-mail the participants
const result = (
await documentsApi.apiDocumentsPost({ ...documentRequest, disablePendingActionNotifications: true })
).data[0];
const actionUrlRequest: DocumentsActionUrlRequest = {
identifier: "81976153069",
emailAddress: "john.wick@mailinator.com",
};
const actionUrl = (await documentsApi.apiDocumentsIdActionUrlPost(result.documentId, actionUrlRequest)).data;
// actionUrl.url: redirect the user to Signer
// actionUrl.embedUrl: pass it to the widget
use Lacuna\Signer\Model\DocumentsActionUrlRequest;
use Lacuna\Signer\Model\DocumentsActionUrlResponse;
// Before createDocument: don't e-mail the participants
$documentRequest->setDisablePendingActionNotifications(true);
$result = $signerClient->createDocument($documentRequest)[0];
$actionUrlRequest = new DocumentsActionUrlRequest();
$actionUrlRequest->setIdentifier("81976153069");
$actionUrlRequest->setEmailAddress("john.wick@mailinator.com");
$actionUrlResponse = new DocumentsActionUrlResponse($signerClient->getActionUrl($result->getDocumentId(), $actionUrlRequest));
// $actionUrlResponse->getUrl(): redirect the user to Signer
// $actionUrlResponse->getEmbedUrl(): pass it to the widget
To require the e-mail code, also set RequireEmailAuthentication = true (C#), setRequireEmailAuthentication(true) (Java and PHP) or requireEmailAuthentication: true (Node.js) on the action URL request.
Examples on GitHub: C# · Java · PHP · Node.js
Pre-positioning a signature on the document
During the creation of a document, it is possible to place each participant's signature in a specific place in the document. All definitions are done during the creation of the flowAction: there are options to define the page and the location of the signature within it.
Examples on GitHub: C# · Java · PHP · Node.js
Signer positioning of the signature
Available from version 3.0.0. Two fields control where the signature's visual representation (the signature image on the PDF) goes:
| Field | Description |
|---|---|
signerPositioningEnabled | If false, the signer cannot position their own signature. If they send a position, the API responds with the SignerPositioningNotAllowed error. |
automaticSignaturePositioning | What the Signer does with a signature nobody positioned: Disabled (leaves it off the PDF), Footnote (places it on the document footer) or SignatureSheet (places it on a signature sheet at the end of the PDF). |
Send the fields when you create the document. A null or missing field uses the organization's current setting. The document keeps the value: a later change in the organization does not change the document. You cannot combine signerPositioningEnabled set to false with Disabled: the API responds with the SignerPositioningIncompatibleWithAutomaticPositioningDisabled error.
To change both fields after creation, send a PUT. It requires the same permission as editing the flow:
PUT /api/documents/b12cb1b2-5d6e-40b2-a050-097d068c4c11/signer-positioning
{
"signerPositioningEnabled": false,
"automaticSignaturePositioning": "SignatureSheet"
}
You can make this change only before a participant signs or approves. After that, the API responds with the SignerPositioningLockedAfterSignature error. The change also fails on canceled, expired, refused or paused documents.
When you edit the flow of an already signed document (POST /api/documents/{id}/flow), the signature sheet may have no room for a new participant. In that case, the API responds with the SignatureSheetFull error. To accept the edit anyway, send ?acceptFootnoteVisualRepresentation=true. The Signer places the signatures that do not fit on the page footer and leaves out those participants' optional initials.
Merge multiple files into a single document (envelope)
It is possible to upload multiple files and merge them all into a single document (envelope). To do so, add the isEnvelope=true parameter and an envelope name in EnvelopeName.
Merging only works if all uploaded files are PDFs.
Examples on GitHub: C# · Java · PHP · Node.js
Next steps
With the document created, see how to track its status and send reminders.