Agreement workflows with Adobe Document Services APIs in Node.js

Many business applications and processes require documentation like proposals and agreements. PDF documents ensure files are more secure and less modifiable. They also provide digital signature support so your clients can quickly and easily complete their documents. Adobe Document Services APIs easily incorporate PDF capabilities into your web applications.

We previously stepped through a high-level example of the APIs available. In this article, we will add PDF services to a Node.js application to digitize an agreement process.

Setting up Adobe Document Services

To get started, set up credentials to use Adobe Document Services. We can register an account and use the Node.js Quickstart to verify our credentials work before integrating the functionality into a larger application.

First, you’ll need an Adobe developer account. Then, on the Get Started page, select the Get Started option under Create New Credentials. You can sign up for their free trial which provides 1,000 Document Transactions that can be used over 6 months.

Image of Create New Credentials

On the following Create New Credentials page, you are prompted to decide between the PDF Embed API and the PDF Tools API.

Select PDF Tools API.

Enter a name for the application and check the box labeled Create personalized code sample. Checking this box automatically embeds your credentials in the code sample. If you leave this box unchecked, you must manually add your credentials to the application.

Select Node.js for the application type and click Create Credentials.

A few moments later, a .zip file will begin to download with a sample project including your credentials. The Node.js package for document services is already included as part of the sample project code.

Image of Selecting PDF Tools API Credentials

Configuring the sample project manually

If you choose to not download a sample project from the Create New Credentials page, you can also set up the project manually.

Download the code (without your credentials embedded) from GitHub. If you use this version of the code, you need to add your credentials to the pdftools-api-credentials.json file before use:

{
  "client_credentials": {
    "client_id": "<client_id>",
    "client_secret": "<client_secret>"
  },
  "service_account_credentials": {
    "organization_id": "<organization_id>",
    "account_id": "<technical_account_id>",
    "private_key_file": "<private_key_file_path>"
  }
}

For your own application, you’ll need to copy the private key file and credentials files to your application source.

You must also install the Node.js package for document services. To install the package, use the following command:

npm install --save @adobe/documentservices-pdftools-node-sdk

Setting up logging

The samples here use Express for the application framework. They also use log4js for application logging. With log4js, you can easily direct logging to the console or out to a file:

const log4js = require('log4js');
const logger = log4js.getLogger();
log4js.configure( {
	appenders: { fileAppender: { type:'file', filename: './logs/applicationlog.txt'}},
	categories: { default: {appenders: ['fileAppender'], level:'info'}}
});
 
logger.level = 'info';
logger.info('Application started')

The above code writes logged data to a file in ./logs/applicationlog.txt. If we want it to write to the console instead, we can comment out the call to log4js.configure.

Converting Word files to PDF

Agreements and proposals are often written in a productivity application, like Microsoft Word. We can add functionality for your application to accept documents in this format and convert the document to PDF. Let’s look at how to upload and save a document in an Express application and save it to the file system.

In the application’s HTML, add a file element and a button for starting the upload:

<input type="file" name="source" id="source" />
<button onclick="upload()" >Upload</button>

In the page’s JavaScript, we can upload the file asynchronously using the fetch function:

function upload()
{
  let formData = new FormData();
  var selectedFile = document.getElementById('source').files[0];
  formData.append("source", selectedFile);
  fetch('documentUpload', {method:"POST", body:formData});
}

Choose a folder to accept your uploaded files. The application will need a path to this folder. Find the absolute path by using a relative path joined with __dirname:

const uploadFolder = path.join(__dirname, "../uploads");

Since the file is submitted via post, we need to respond to a post message on the server side:

router.post('/', (req, res, next) => {
  console.log('uploading')
  if(!req.files || Object.keys(req.files).length === 0) {
  return res.status(400).send('No files were uploaded');
  }
	
  const uploadPath = path.join(uploadFolder, req.files.source.name);
  var buffer = req.files.source.data;
  var result = {"success":true};
  fs.writeFile(uploadPath, buffer, 'binary', (err)=> {
    if(err) {
      result.success = false;
    }
    res.json(result);
  });       
});

After this function executes, the file saves in the applications upload folder and is available for further processing.

We now want to convert the file from its native format to PDF. The sample code you downloaded earlier contains a script named create-pdf-from-docx.js for converting a document to PDF. The following function, convertDocumentToPDF, takes an uploaded document and converts it to a PDF in a different folder:

function convertDocumentToPDF(sourcePath, destinationPath)
{	
  try {   
    const credentials = PDFToolsSDK.Credentials
    .serviceAccountCredentialsBuilder()
    .fromFile("pdftools-api-credentials.json")
    .build();
 
    const executionContext = 
      PDFToolsSDK.ExecutionContext.create(credentials),
    createPdfOperation = PDFToolsSDK.CreatePDF.Operation.createNew();
 
    const docxReadableStream = fs.createReadStream(sourcePath);
    const input = PDFToolsSDK.FileRef.createFromStream(
      docxReadableStream, 
      PDFToolsSDK.CreatePDF.SupportedSourceFormat.docx);
    createPdfOperation.setInput(input);
 
    createPdfOperation.execute(executionContext)
    .then(result => result.saveAsFile(destinationPath))
    .catch(err => {    	
      logger.erorr('Exception encountered while executing operation');        
    })
  }
  catch(err) {    	
    logger.error(err);
  }
}

You may notice a general pattern with the code:

The code builds a credentials object and an execution context, initializes some operation, then executes the operation with the execution context. You will see this pattern throughout the sample code.

By making a few additions to the upload function so it calls this function, the Word documents that users upload are now automatically converted to PDF.

The following code builds the destination path for the converted PDF and initiates the conversion:

const documentFolder = path.join(__dirname, "../docs");
var extPosition = req.files.source.name.lastIndexOf('.') - 1;
if(extPosition < 0 ) {
  extPosition = req.files.source.name.length
}
const destinationName = path.join(documentFolder,  
  req.files.source.name.substring(0, extPosition) + '.pdf');
console.log(destinationName);
 
logger.info('converting to ${destinationName}')
  convertDocumentToPDF(uploadPath, destinationName);

Converting other file types to PDF

The document toolkit converts other formats to PDF, such as static HTML, another common document type. The toolkit accepts HTML documents packaged as a .zip file with all resources referenced by the document (CSS files, images, and other files) in the same .zip file. The HTML document itself must be named index.html and placed in the root of the .zip file.

If we have a .zip file containing HTML we want to convert, we can use the following code:

//Create an HTML to PDF operation and provide the source file to it
htmlToPDFOperation = PDFToolsSdk.CreatePDF.Operation.createNew(); 	
const input = PDFToolsSdk.FileRef.createFromLocalFile(sourceZipFile);
htmlToPDFOperation.setInput(input);
 
// custom function for setting options
setCustomOptions(htmlToPDFOperation);
 
// Execute the operation and Save the result to the specified location.
htmlToPDFOperation.execute(executionContext)
  .then(result => result.saveAsFile(destinationPdfFile))
  .catch(err => {
    logger.error('Exception encountered while executing operation');
});

The function setCustomOptions specifies other PDF settings, such as the page size. Here, you can see the function sets the page size to 11.5 by 11 inches:

const setCustomOptions = (htmlToPDFOperation) => {    
  const pageLayout = new PDFToolsSdk.CreatePDF.options.PageLayout();
  pageLayout.setPageSize(11.5, 8);

  const htmlToPdfOptions = 
    new PDFToolsSdk.CreatePDF.options.html.CreatePDFFromHtmlOptions.Builder()
    .includesHeaderFooter(true)
    .withPageLayout(pageLayout)
    .build();
  htmlToPDFOperation.setOptions(htmlToPdfOptions);
};

Opening an HTML document containing some terms, we get the following within the browser:

Image of Computer Terms

The source for this document is composed of a CSS file and an HTML file:

Image of CSS and HTML file

After processing the HTML file, we have the same text in PDF format:

PDF file of Computer Terms

Appending pages

Another common operation with PDF files is appending pages to the end that may have standard text, such as a list of terms. The document toolkit can combine several PDF documents into a single document. If we have a list of document paths (here in sourceFileList), we can add each file’s file references to an object for a combine operation.

When the combine operation executes, it provides a single file containing all of the source content. We can use saveAsFile on the object to persist the file to storage.

const executionContext = PDFToolsSDK.ExecutionContext.create(credentials);
var combineOperation = PDFToolsSDK.CombineFiles.Operation.createNew();
 
sourceFileList.forEach(f => {
  var combinedSource = PDFToolsSDK.FileRef.createFromLocalFile(f);
  console.log(f);
  combineOperation.addInput(combinedSource);
});
	
 
combineOperation.execute(executionContext)
  .then(result=>result.saveAsFile(destinationFile))
  .catch(err => {
    logger.error(err.message);
});	

Displaying PDF documents

We’ve performed several operations on PDF files, but ultimately, your user needs to view the documents. You can easily embed the document into a webpage using Adobe’s PDF Embed API.

On the page that will display the PDF, add a <div /> element to hold the document and give it an ID. We will use this ID shortly. In the web page, include a <script /> reference to the Adobe JavaScript library:

<script src="https://documentcloud.adobe.com/view-sdk/main.js"></script>

The last bit of code you need is a function that displays the document once the Adobe PDF Embed API JavaScript is loaded. We receive a notification that the script is loaded through an adobe_dc_view_sdk.ready event, then create a new AdobeDC.View object. This object needs your Client ID and the ID of the element we created earlier. Find your client ID in the Adobe Developer Console. When you view the settings for the application you created when generating credentials earlier, the Client ID will display there.

Image of API Client Key

Other PDF options

Adobe PDF Embed API demo enables you to preview the various other options for embedding PDF documents.

Image of embedding PDF options

You can turn various options on and off and immediately see how they render. When you find a combination you like, click the </> Generate Code button to generate the actual HTML code using those options.

Image of Code Preview

Adding digital signatures and security

Once a document is ready, you can add in digital signatures for approval using Adobe Sign. This functionality works a bit differently than the functionality we have used thus far. For digital signatures, an application must be configured to use OAuth for user authentication.

The first step in setting up your application will be to register your application to use OAuth for Adobe Sign. Once signed in, navigate to the screen for creating new applications by clicking on Account, then open the Adobe Sign API section, and click API Applications to open the list of applications you have registered.

Image of first step in registering your application

To create a new application entry, click on the plus icon in the upper-right corner.

Image of the plus icon in upper-right corner of screen

In the window that opens, enter an application name and display name. Select Customer for the domain, then click Save.

Image of where to enter application name and display name

After the application is created, we can select it from the list and click on Configure OAuth for Application. Select the options. In the Redirect URL, enter the URL for your application. You can enter multiple URLs here. For the application we’re testing, the value is:

http://localhost:3000/signed-in 

The process of using OAuth to obtain a token is standard. Your application directs a user to a URL for signing in. After the user successfully signs in,
they are redirected back to the application with additional information in the page’s query parameters.

For the login URL, your application must pass your Client ID, redirect URL, and a list of the scopes needed.

The pattern for the URL looks like the following:

https://secure.adobesign.com/public/oauth?
  redirect_uri=&
  response_type=code&
  client_id=&
  scope=

The user will be prompted to sign into their ID for Adobe Sign. After signing in, they are asked whether or not to provide permissions to the application.

Image of confirm access screen

If the user clicks Allow Access on the redirect URL, a query parameter named code passes the authorization code:

https://YourServer.com/?code=<authorization_code>&api_access_point=https://api.adobesign.com&web_access_point=https://secure.adobesign.com

Posting this code to the Adobe Sign server along with your client ID and client secret provides an access token to access the service. Save the values in the parameters api_access_point and web_access_point. These values are used for further requests.

var requestURL = ' ${api_access_point}oauth/token?code=${code}'
  +'&client_id=${client_id}'
  +'&client_secret=${client_secret}&'
  +'redirect_uri=${redirect_url}&'
  +'grant_type=authorization_code';
request.post(requestURL, {form: { }
}, (err,response,body)=>{            	
    var token_response = JSON.parse(body)
    var access_token = token_response.access_token;
    console.log(access_token);
});

When a document requires a signature, the document must first be uploaded. Your application can upload the document to the api_access_point value that was received while requesting the OAUTH token. The endpoint is /api/rest/v6/transientDocuments. The request data looks like the following:

POST /api/rest/v6/transientDocuments HTTP/1.1
Host: api.na1.adobesign.com
Authorization: Bearer MvyABjNotARealTokenHkYyi
Content-Type: multipart/form-data
Content-Disposition: form-data; name=";File"; filename="MyPDF.pdf"
<PDF CONTENT>

Within your application, build the request with the following code:

var uploadRequest = {
  'method': 'POST',
  'url': '${oauthParameters.signin_domain}/api/rest/v6/transientDocuments',
  'headers': {
    'Authorization': 'Bearer  ${auth_token}'
  },
  formData: {
    'File': {
      'value': fs.createReadStream(documentPath),
      'options': {
        'filename': fileName,
        'contentType': null
      }
    }
  }
};
 
request(uploadRequest, (error, response) => {
  if (error) throw new Error(error);
  var jsonResponse = JSON.parse(response.body);
  var transientDocumentId = jsonResponse.transientDocumentId;
  logger.info('transientDocumentId:', transientDocumentId)
});

The request returns a transientID value. The document has been uploaded, but is not sent yet. To send the document, use the transientID to request to send the document.

Start by building a JSON object containing the information for the document to be signed. In the following, the variable transientDocumentId contains the ID from the above code and agreementDescription contains text describing the agreement needing signature. The people to sign the document are listed in participantSetsInfo by their email address and role.

var requestBody = {
  "fileInfos":[
    {"transientDocumentId":transientDocumentId}],
    "name":agreementDescription,
    "participantSetsInfo":[
      {"memberInfos":[{"email":"user@domain.com"}],
       "order":1,"role":"SIGNER"}
    ],
    "signatureType":"ESIGN","state":"IN_PROCESS"
};

Sending this web request builds the signing request and returns a JSON object with an ID for the agreement request:

request(requestBody, function (error, response) {
  if (error) throw new Error(error);
  var JSONResponse = JSON.parse(response.body);
  var requestId = JSONResponse.id;
});

If the signers forget to sign and need another notification email, send the notifications again using the ID received earlier. The only difference is that you’ll also need to add the participant IDs of the parties. You can get the participant IDs by sending a GET request to /agreements/{agreementID}/members.

To request to send the reminder, first build a JSON object describing the request. The minimal object needs a list of the participant IDs and a status for the reminder (“ACTIVE”, “COMPLETE”, or “CANCELLED”).

The request can optionally have additional information, such as a value for “note” that will display to the user, a delay (in hours) to wait until sending the reminder (in firstReminderDelay), and a reminder frequency (in the field “frequency”), which accepts values such as DAILY_UNTIL_SIGNED, EVERY_THIRD_DAY_UNTIL_SIGNED, or WEEKLY_UNTIL_SIGNED.

var requestBody = {
  //participantList is an array of participant ID strings
  "recipientParticipantIds":participantList
  ,"status":"ACTIVE",
  "note":"This is a reminder to sign out important agreement."
}
 
var reminderRequest = {
  'method': 'POST',
  'url': '${oauthParameters.signin_domain}/api/rest/v6/agreements/${agreementID}/reminders',
  'headers': {
    'Authorization': `Bearer ${access_token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(requestBody)
 
};

request(reminderRequest, function (error, response) {
});

And that’s all it takes to send a reminder request.

Image of Web Form

Creating Web Forms

You can also use the Adobe Sign API to create Web Forms. Web Forms enable you to embed a form within a web page or link directly to it. Once a Web Form is created, it also displays among the web forms in your Adobe Sign console. You can create new Web Forms with DRAFT status for incremental building, AUTHORING status for editing the web form fields, and ACTIVE status to immediately host the form.

Image of Web Form in the Adobe Sign Manage screen

To create a Web Form use the form transientDocumentId. Decide on a title for the form and a status in which to initialize it.

var requestBody = {
  "fileInfos": [
    {
      "transientDocumentId": transientDocumentId
    }
  ],
  "name": webFormTitle,
  "state": status,
  "widgetParticipantSetInfo": {
    "memberInfos": [ { "email": "" } ],
    "role": "SIGNER"
  }
}
var createWebFormRequest = {
  'method': 'POST',
  'url': `${oauthParameters.signin_domain}/api/rest/v6/widgets`,
  'headers': {
    'Authorization': `Bearer ${access_token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(requestBody)
}
request(createWebFormRequest, function (error, response) {
  var jsonResp = JSON.parse(response.body);
  var webFormID = jsonResp.id;
});

You can now embed or link to your document.

Next steps

As you can see from the quick starts and the provided code, it is easy to implement PDF and digital document approval processes using Node with the Adobe Document Services APIs. Adobe’s APIs integrate into your existing client applications seamlessly.

To discover the required scopes for a call, or to see how the call is built, you can build sample calls from the Rest API documentation. The Quickstarts also demonstrate other functionality and file formats the Adobe Document Services APIs processes.

You can add a multitude of PDF capabilities to your applications, enabling your users to quickly and easily view and sign their documents and much more. To start, check out Adobe Document Services today.

On this page

Adobe Summit Banner

A virtual event April 27-28.

Expand your skills and get inspired.

Register for free
Adobe Summit Banner

A virtual event April 27-28.

Expand your skills and get inspired.

Register for free
Adobe Maker Awards Banner

Time to shine!

Apply now for the 2021 Adobe Experience Maker Awards.

Apply now
Adobe Maker Awards Banner

Time to shine!

Apply now for the 2021 Adobe Experience Maker Awards.

Apply now