Send Google Doc To Adobe Sign API TransientDocuments Endpoint (v6)


It has taken almost 2 days to try and get a simple Google Doc to Adobe Sign’s /transientDocuments endpoint. Unfortunately, there are no simple JavaScript samples available on the web that are easy to follow.

According to Adobe Sign’s API documentation for the transientDocument endpoint all that is needed are Authorization, x-api-user, and x-on-behalf-of-user header properties, and in the form body: File-Name, Mime-Type and File.

Simple enough.

But how can you send this using Google Apps Script’s UrlFetchApp?

With Google’s own documentation on the UrlFetchApp class, for the fetch() function it just needs two parameters: url and params.

So where do you get the url from?

Insert Google OAuth2 Library

Well, first you need to import Google’s OAuth2 library into your Google Apps Script project. Directions on how to do that are fairly easy to follow on their GitHub repo here.

If you’re using clasp to code your script, you just need to modify your appsscript.json file in your project to look something like this:

{
  // ...
  "dependencies": {
    "libraries": [{
      "libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF",
      "version": "41",
      "userSymbol": "OAuth2"
    }]
  },
  // ...
}

If you want to know what version to apply go back to the GitHub repo and click on the master button and then click on Tags. Currently, the top tag is labelled 41 so this is the version number I’ve inserted into my code above.

Screenshot of Google OAuth2 Github library showing how to get latest version from the Tags section
To find the latest version number from the OAuth 2 library on GitHub click master then Tags

Insert Adobe Sign Sample Code

Next, we want to copy the sample Adobe Sign code already provided in this repo. From the Google OAuth2 Github repo home page, click on the samples folder, then click on the AdobeSign.gs file – here’s a direct link.

Copy this code and create a separate file in your Google App Script project. Label the file something meaningful like AdobeSign.

Modify Run Function

I made some slight modifications to the run function in this file to make it easier to call multiple endpoints, you might want to apply the same:

/** @type {String} */
const CLIENT_ID = '...';
/** @type {String} */
const CLIENT_SECRET = '...';
/** @type {String} */
const API_ACCESS_POINT_KEY = 'api_access_point';
/** @type {String} */
const API_ROOT_PATH = 'api/rest/v6';

/**
 * Authorizes and makes a request to the Adobe Sign API.
 * @param {String} endpoint
 * @param {Object} params
 * @returns {?Object}
 */
function run(endpoint, params) {
    const service = getService();
    if (service.hasAccess()) {
        // Get access token
        /** @type {String} */
        const auth = `Bearer ${service.getAccessToken()}`;
        if (params.hasOwnProperty('headers')) {
            params.headers['Authorization'] = auth;
        } else {
            params.headers = {
                'Authorization': auth
            }
        }
        // Retrieve the API access point from storage.
        const apiAccessPoint = service.getStorage().getValue(API_ACCESS_POINT_KEY);
        /** @type {String} */
        const url = apiAccessPoint + API_ROOT_PATH + endpoint;
        /** @type {String} */
        const response = UrlFetchApp.fetch(url, params);
        /** @type {Object} */
        const result = JSON.parse(response.getContentText());
        return result;
    } else {
        if (service.getLastError()) {
            Logger.log(service.getLastError());
        }
        const authorizationUrl = service.getAuthorizationUrl();
        Logger.log('Open the following URL and re-run the script: %s',
            authorizationUrl);
    }
}

Using the above code would simply require a call to the run function with the endpoint and parameters being issued to UrlFetchApp.fetch(). For example, when calling this function in my code I would do so with the following:

/** @type {Object} */
const adobeDocId = run('/transientDocuments', params);

Create & Configure API Application In Adobe Sign

The last edit required is to change the setScope property in the getService function. This needs to match the API Application you have created in Adobe Sign. If you haven’t already done so, log in to Adobe Sign and navigate to your Account section, click on Adobe Sign API and then from the drop-down menu click on API Applications.

From here you want to create a new application (look for a plus symbol). You will be asked to provide a name and a display name, and who will have access to this application. Once you’ve created the application, click on the row in the table presented, and then a sub-menu will pop-up at the top of the table. Click on the link Configure OAuth for Application.

In this area you will need to populate the Redirect URI field with the following schema:

https://script.google.com/macros/d/<INSERT YOUR SCRIPT'S ID>/usercallback

You can find your script’s ID in the URL of your Google Apps Script project, or if you’re using clasp in the clasp.json file.

Lastly, in the configuration window you will want to enable the features that are needed for your application to work properly. Copy the Scope variable names that you have ticked and in your Google Apps Script code in the getService function amend the setScope property to represent each of the features you have enabled, separating each scope with a space.

For example, if I have enabled the following features in the configuration: Agreement Read, Agreement Write, Agreement Send, Library Read, User Write then this would correspond to the following for my setScope property in my Google Apps Script:

function getService(optApiAccessPoint) {
  var service = OAuth2.createService('AdobeSign')
      // Set the endpoint URLs.
      .setAuthorizationBaseUrl('https://secure.echosign.com/public/oauth')

      // Set the client ID and secret.
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)

      // Set the name of the callback function that should be invoked to
      // complete the OAuth flow.
      .setCallbackFunction('authCallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())
      .setPropertyStore(PropertiesService.getScriptProperties())

      // Set the scopes.
      .setScope('agreement_write agreement_read agreement_send library_read user_write');

Connect Adobe Sign Application & Google Apps Script

Next, we need to get our code on Google Apps Script to connect to our new Adobe Sign API application.

To trigger the connection between your Google App Script and Adobe Sign you will need to run the run() function. So within Google Apps open your AdobeSign file in your browser and run the run() function. You should see a prompt in the Logger window below (if you’re using the latest editor) which will state something like:

Open the following URL and re-run the script: https://...

Copy the URL and pop it into another browser window and log in to Adobe Sign and follow the prompts.

If you happen to get some errors, double check you’ve copied the correct scriptId into the callback URL, and double-check the scopes you have inserted.

Hopefully, if you’ve done everything correctly you should see your browser window state the word Success!

Set UrlFetchApp.Fetch() Parameters

I’ve you’ve arrived at this step, you’re doing well. At this point you should have the following set up:

  • Google OAuth2 library attached to your Google App Script project
  • Inserted AdobeSign sample code, with modifications
  • Created an Adobe Sign API application
  • Connected the Google Apps Script code to your Adobe Sign API Application

This last step is sending our Google Doc to Adobe Sign and has caused me the most angst.

First, if you don’t already have a Google Doc created that you would like to test, create a basic one now. Grab its ID from the URL as we will need this to test whether our connection is working.

https://docs.google.com/document/d/<Google Doc ID is found here>/edit

Then create another code file in your Google Apps Script and paste the following code:

function debug() {
    /** @type {DocumentApp.Document} */
    const doc = DocumentApp.openById('Insert Google Doc Id here')
    /** @type {Blob} */
    const pdfDoc = doc.getBlob();
    /** @type {String} */
    const fileName = "This is just a test";

    /** @type {Object} */
    const params = {};
    params.muteHttpExceptions = true;
    params.method = 'POST';
    params.payload = {
        'File-Name': fileName,
        'File': pdfDoc
    };

    /** @type {Object} */
    const adobeDocId = run('/transientDocuments', params);
    Logger.log(adobeDocId.transientDocumentId);
}

Edit the doc variable such that the Google Doc ID you copied above is inserted in the openById function’s parameter.

Run the debug code by pressing the Debug button and if all is well you will see down the bottom of the screen in the Logger area a hash representing the transientDocumentId.

Summary

The reason why the above code took so long for me to formulate was I assumed I had to do more with the params property passed into the UrlFetchApp.fetch function. I didn’t realise that Google applies all the necessary parameters when payload information is sent through as a Javascript object.

Even within Google’s own fetch function documentation, in their second example, they note in the comments section:

// Because payload is a JavaScript object, it is interpreted as
// as form data. (No need to specify contentType; it automatically
// defaults to either 'application/x-www-form-urlencoded'
// or 'multipart/form-data')

Therefore, you only need the minimal information required for Adobe Sign, such as the File property for the payload object. Google will automatically set the Content-Type as multiform/form-data and provide all the necessary boundary settings.

One other final issue I had was thinking the File: property had to be in Bytes rather than as a Blob. There is a lot of documentation that asserts the data needs to be in bytes, but again, Google converts this file from a blob into bytes when it transmits the information. There’s no need for the user to convert the document into bytes and to insert that into the payload property.

Once I read and understood what I was doing with the UrlFetchApp class it was quickly and easily solved.

Recent Posts