0

I am creating an embedded solution. At some point in my app, the user can upload a file. I have a required attachment field in my template. If the user uploaded a file in my app, I want to attach it during envelope creation, otherwise I want the docusign signing view to force attachment. In either case, I want it to appear identical to the user receiving the final signed document (ie: as a separate attachment). I'm looking for XML examples of how to achieve this. This post had a recommended solution similar to what I'm looking to do, but there was no code: Attachments on iPad in iFrame docusign and this post referred to an example that might be what I want, but the link is broken: What is expected for the EnvelopeAttachment parameters of "Type" and "Label"?

or if someone could point me to the current location of:

http://www.docusign.com/content/e-signature-code-walkthrough-signer-attachments

Non-working example of what I'm looking for:

public void AddAttachment2Envelope(string envelopeID, byte[] attachment, string attachmentname)
{
    string url = baseURL + "/envelopes/" + envelopeID + "/documents";
    string requestBody =
    "--AAA" + "\r\n" +
    "Content-Type: application/xml" + "\r\n" +
    "Content-Disposition: form-data" +  "\r\n" +
    "<envelopeDefinition xmlns=\"http://www.docusign.com/restapi\">" +  "\r\n" +
    "<documents>" +  "\r\n" +
    "<document>" + "\r\n" +
    "<documentId>12345</documentId>" +  "\r\n" + //what is documentid here used for?
    "<name>" + attachmentname + "</name>" +  "\r\n" +
    "<order>1</order>" +  "\r\n" + 
    "</document>" +  "\r\n" +
    "</documents>" +  "\r\n" +
    "</envelopeDefinition>" +  "\r\n" +
    "--AAA" +  "\r\n" +
    "Content-Type: application/pdf" +  "\r\n" +
    "Content-Disposition: file; filename=\"String content\"; documentId=10" + "\r\n" + //what is documentid here used for?
    attachment.ToString() + "\r\n" +
    "--AAA";
    HttpWebRequest request = initializeRequest(url, "PUT", requestBody, email, password);
    request.ContentType = "multipart/form-data; boundary=AAA";
    string response = getResponseBody(request);
}

I've also tried the following without success:

public void AddAttachment2Envelope(string envelopeID, byte[] attachment, string attachmentname, Boolean bad)
{
    string ctype = "Content-Type: application/pdf";
    if (attachmentname.ToLower().EndsWith(".jpg"))
        ctype = "Content-Type: image/jpeg";
    else if (attachmentname.ToLower().EndsWith(".png"))
        ctype = "Content-Type: image/png";
    string url = baseURL + "/envelopes/" + envelopeID + "/documents/10";
    string requestBody =
    ctype + "\r\n" +
    "Content-Disposition: file; filename=\"" + attachmentname + "\"; documentId=10" + "\r\n" +
    System.Text.Encoding.Default.GetString(attachment) + "\r\n" + //System.Text.Encoding.Default.GetString(attachment)  //System.Convert.ToBase64String(attachment, 0, attachment.Length)
    "";
    HttpWebRequest request = initializeRequest(url, "PUT", requestBody, email, password);
    request.ContentType = ctype.Replace("Content-Type: ", "");
    request.Headers.Add("Content-Disposition", "file; filename=\"" + attachmentname + "\"; documentId=10");
    string response = getResponseBody(request);
}
public void AddAttachment2Envelope(string envelopeID, byte[] attachment, string attachmentname)
{
    string ctype = "Content-Type: application/pdf";
    if (attachmentname.ToLower().EndsWith(".jpg"))
        ctype = "Content-Type: image/jpeg";
    else if (attachmentname.ToLower().EndsWith(".png"))
        ctype = "Content-Type: image/png";
    string url = baseURL + "/envelopes/" + envelopeID + "/documents";
    string requestBody =
    "--AAA" + "\r\n" +
    "Content-Type: application/xml" + "\r\n" +
    "Content-Disposition: form-data" + "\r\n" +
    //"<envelopeDefinition xmlns=\"http://www.docusign.com/restapi\">" + "\r\n" +
    //"<documents>" + "\r\n" +
    "<document>" + "\r\n" +
    "<documentId>10</documentId>" + "\r\n" +
    "<name>" + attachmentname + "</name>" + "\r\n" +
    "<order>2</order>" + "\r\n" +
    //"<FileExtension>" + System.IO.Path.GetExtension(attachmentname).Replace(".", "").ToLower() + "</FileExtension>" + "\r\n" +
    "</document>" + "\r\n" +
    //"</documents>" + "\r\n" +
    //"</envelopeDefinition>" + "\r\n" +
    "--AAA" + "\r\n" +
    ctype + "\r\n" +
    "Content-Disposition: file; filename=\"" + attachmentname + "\"; documentId=10" + "\r\n" +
    System.Convert.ToBase64String(attachment, 0, attachment.Length) + "\r\n" + //System.Text.Encoding.Default.GetString(attachment)
    "--AAA--";
    HttpWebRequest request = initializeRequest(url, "PUT", requestBody, email, password);
    request.ContentType = "multipart/form-data; boundary=AAA";
    //request.Accept = "multipart/form-data;";
    string response = getResponseBody(request);
}
public void AddAttachment2Envelope(string envelopeID, byte[] attachment, string attachmentname, int bad)
{
    string url = baseURL + "/envelopes/" + envelopeID + "/documents";
    string requestBody =
    // "<envelopeDefinition xmlns=\"http://www.docusign.com/restapi\">" +  "\r\n" +
    // "<documents>" + "\r\n" +
    "<document>" + "\r\n" +
    "<documentId>10</documentId>" + "\r\n" +
    "<name>" + attachmentname + "</name>" + "\r\n" +
    "<order>2</order>" + "\r\n" +
    "<FileExtension>" + System.IO.Path.GetExtension(attachmentname).Replace(".", "").ToLower() + "</FileExtension>" + "\r\n" +
    "<documentBase64>" + System.Convert.ToBase64String(attachment, 0, attachment.Length) + "</documentBase64>" + "\r\n" +
    "</document>" + "\r\n" +
    // "</documents>" + "\r\n" +       
    // "</envelopeDefinition>" +  "\r\n" +
    "";
    //requestBody = requestBody.Replace("\r\n", "");
    HttpWebRequest request = initializeRequest(url, "PUT", requestBody, email, password);
    string response = getResponseBody(request);
}
Community
  • 1
  • 1
  • Shorter version: I have signer attachments that may be collected in my app pre-envelope, what is the XML markup/etc needed during createenvelope to attach these ? – user3757872 Sep 23 '14 at 13:24
  • I don't believe that's possible- signer attachments can only be attached by a signer when it's their turn to sign, hence not when the envelope is in Draft state. – Ergin Sep 23 '14 at 18:51
  • Thanks for answering Ergin, but did something change in the API since your answers on the posts that I linked to ? I believe I am looking to do what you had recommended here: "you could use the Embedding feature to design whatever UI around the signing experience you'd like and in that case you can control the order of operations (i.e. have them upload the image first before clicking a button to sign) and you can then resize the image and add it to the envelope. " – user3757872 Sep 24 '14 at 19:10
  • Or possibly here (from your other post): "If you want to add another file to the envelope that is in the draft state and you want all recipients to be able to see it, then you would just add another document to the envelope but don't configure any tabs on that document. Then the recipients would only be able to read that document and not perform any actions on it." – user3757872 Sep 24 '14 at 19:11
  • I think you're mis-reading my comments on the other post, I was not saying that the Embedding feature allows you to control the UI, I was saying you can design whatever UI you want in your app and do everything by making API calls from your UI one by one, then when it's time to sign you can use Embedding to create a URL and hook up to a button in your UI. – Ergin Sep 26 '14 at 17:06
  • So for instance, through your UI you make one API call to create a draft envelope (ie status = "created"). Then you let your users upload an attachment through your UI again, and under the hood you make an API call to add to that uploaded file to the envelope (while it's still in draft state). Make sense? – Ergin Sep 26 '14 at 17:07
  • No, I understood completely. This: "and under the hood you make an API call to add to that uploaded file to the envelope (while it's still in draft state)." is what I'm looking for an xml code sample of how to do. You had pretty much said the same thing in the 2 other posts, but the only link to a code sample is a dead link. – user3757872 Sep 26 '14 at 17:25
  • Let me know if my latest post resolved your issues here? – Ergin Oct 03 '14 at 23:33
  • I was hoping for a more spoon-fed example, I'll have to experiment with this. I'm guessing that after I create the envelope with a POST call, I can immediately use a PUT to add the attachments ? Will I need to pass all of the existing documents/etc XML with that or only the new attachment ? Will attachments added in this manner be visible to everyone in the chain ? – user3757872 Oct 06 '14 at 12:14
  • I added comments/questions to your answer below, and updated my question with my first attempt at implementing this. – user3757872 Oct 06 '14 at 13:13
  • At this point I've tried dozens/hundreds of variations on syntax and approach without success. Updated question with more examples. The Docusign documentation is severely lacking in this regard, the API browser gives no example of the PUT request, and I've found at least 4 different syntax/methods elsewhere in the documentation and on SO posts. Has anyone ever gotten this to work ? – user3757872 Oct 17 '14 at 16:03
  • I finally found time to return to this and got this working. I'm adding a full code sample in C# that successfully adds a new document to an existing envelope, without replacing the existing doc. The trick is to make sure you don't overwrite your documentIds (i.e. if the envelope contains `documentId=1` make sure you add `documentId=2`). – Ergin Oct 21 '14 at 18:23

1 Answers1

0

So being careful with the wording of your question here- as mentioned "attachments" are referred to as "Signer attachments" in the DocuSign platform and those can only be added through signer attachment tabs for individual recipients.

On the other hand, we usually refer to "documents" as "Envelope Documents" and as mentioned you can add documents to an envelope when the envelope is in draft state:

REST API v2 Guide - Add Documents to Draft Envelope

This is a PUT call you want to make to the /accounts/{accountId}/envelopes/{envelopeId}/documents URL. Here is a full C# code sample (copied from the DocuSign API Walkthroughs):

using System; 
using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Linq;

namespace DocuSignAPITest
{
    public class AddDocumentToEnvelope
    {
        public static void Main ()
        {
            //---------------------------------------------------------------------------------------------------
            // ENTER VALUES FOR THE FOLLOWING 6 VARIABLES:
            //---------------------------------------------------------------------------------------------------
            string username = "***";            // your account email
            string password = "***";            // your account password
            string integratorKey = "***";           // your account Integrator Key (found on Preferences -> API page)
            string documentName = "***";            // copy document with same name and extension into project directory (i.e. "test.pdf")
            string envelopeId = "***";              // GUID of envelope we will add document to
            string contentType = "application/pdf";     // default content type is PDF
            //---------------------------------------------------------------------------------------------------

            // additional variable declarations
            string baseURL = "";            // - we will retrieve this through the Login API call

            try {
                //============================================================================
                //  STEP 1 - Login API Call (used to retrieve your baseUrl)
                //============================================================================

                // Endpoint for Login api call (in demo environment):
                string url = "https://demo.docusign.net/restapi/v2/login_information";

                // set request url, method, and headers.  No body needed for login api call
                HttpWebRequest request = initializeRequest( url, "GET", null, username, password, integratorKey);

                // read the http response
                string response = getResponseBody(request);

                // parse baseUrl from response body
                baseURL = parseDataFromResponse(response, "baseUrl");

                //--- display results
                Console.WriteLine("\nAPI Call Result: \n\n" + prettyPrintXml(response));

                //============================================================================
                //  STEP 2 - Add document to draft envelope
                //============================================================================

                // append "/envelopes/{envelopeId}/documents" to baseURL
                url = baseURL + "/envelopes/" + envelopeId + "/documents";

                // construct the request body 
                string jsonBody = 
                    "{\"documents\": [" + 
                        "{\"documentId\": \"2\"," +
                        "\"name\": \"test.pdf\"," + 
                        "\"order\": \"2\"" +
                        "}]" +
                    "}";

                Console.WriteLine("\nJSON: \n\n" + jsonBody);

                // set request url, method, headers.  Don't set the body yet, we'll set that separelty after
                // we read the document bytes and configure the rest of the multipart/form-data request
                request = initializeRequest( url, "PUT", null, username, password, integratorKey);

                // some extra config for this api call
                configureMultiPartFormDataRequest(request, jsonBody, documentName, contentType);

                // read the http response
                response = getResponseBody(request);

                //--- display results
                Console.WriteLine("\nAPI Call Result: \n\n" + prettyPrintXml(response));
//              Console.WriteLine("\nAPI Call Result: \n\n" + response);
            }
            catch (WebException e) {
                using (WebResponse response = e.Response) {
                    HttpWebResponse httpResponse = (HttpWebResponse)response;
                    Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
                    using (Stream data = response.GetResponseStream())
                    {
                        string text = new StreamReader(data).ReadToEnd();
                        Console.WriteLine(prettyPrintXml(text));
                    }
                }
            }
        } // end main()

        //***********************************************************************************************
        // --- HELPER FUNCTIONS ---
        //***********************************************************************************************
        public static HttpWebRequest initializeRequest(string url, string method, string body, string email, string password, string intKey)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create (url);
            request.Method = method;
            addRequestHeaders( request, email, password, intKey );
            if( body != null )
                addRequestBody(request, body);
            return request;
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static void addRequestHeaders(HttpWebRequest request, string email, string password, string intKey)
        {
            // authentication header can be in JSON or XML format.  XML used for this walkthrough:
            string authenticateStr = 
                "<DocuSignCredentials>" + 
                    "<Username>" + email + "</Username>" +
                    "<Password>" + password + "</Password>" + 
                    "<IntegratorKey>" + intKey + "</IntegratorKey>" + 
                    "</DocuSignCredentials>";
            request.Headers.Add ("X-DocuSign-Authentication", authenticateStr);
            request.Accept = "application/xml";
            request.ContentType = "application/xml";
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static void addRequestBody(HttpWebRequest request, string requestBody)
        {
            // create byte array out of request body and add to the request object
            byte[] body = System.Text.Encoding.UTF8.GetBytes (requestBody);
            Stream dataStream = request.GetRequestStream ();
            dataStream.Write (body, 0, requestBody.Length);
            dataStream.Close ();
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static void configureMultiPartFormDataRequest(HttpWebRequest request, string jsonBody, string docName, string contentType)
        {
            // overwrite the default content-type header and set a boundary marker
            request.ContentType = "multipart/form-data; boundary=BOUNDARY";

            // start building the multipart request body
            string requestBodyStart = "\r\n\r\n--BOUNDARY\r\n" +
                "Content-Type: application/json\r\n" +
                    "Content-Disposition: form-data\r\n" +
                    "\r\n" +
                    jsonBody + "\r\n\r\n--BOUNDARY\r\n" +   // our json formatted request body
                    "Content-Type: " + contentType + "\r\n" +
                    "Content-Disposition: file; filename=\"" + docName + "\"; documentId=2\r\n" +
                    "\r\n";
            string requestBodyEnd = "\r\n--BOUNDARY--\r\n\r\n";

            // read contents of provided document into the request stream
            FileStream fileStream = File.OpenRead(docName);

            // write the body of the request
            byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(requestBodyStart.ToString());
            byte[] bodyEnd = System.Text.Encoding.UTF8.GetBytes(requestBodyEnd.ToString());
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(bodyStart, 0, requestBodyStart.ToString().Length);

            // Read the file contents and write them to the request stream.  We read in blocks of 4096 bytes
            byte[] buf = new byte[4096];
            int len;
            while ((len = fileStream.Read(buf, 0, 4096) ) > 0) {
                dataStream.Write(buf, 0, len);
            }
            dataStream.Write(bodyEnd, 0, requestBodyEnd.ToString().Length);
            dataStream.Close();
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static string getResponseBody(HttpWebRequest request)
        {
            // read the response stream into a local string
            HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse ();
            StreamReader sr = new StreamReader(webResponse.GetResponseStream());
            string responseText = sr.ReadToEnd();
            return responseText;
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static string parseDataFromResponse(string response, string searchToken)
        {
            // look for "searchToken" in the response body and parse its value
            using (XmlReader reader = XmlReader.Create(new StringReader(response))) {
                while (reader.Read()) {
                    if((reader.NodeType == XmlNodeType.Element) && (reader.Name == searchToken))
                        return reader.ReadString();
                }
            }
            return null;
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        public static string prettyPrintXml(string xml)
        {
            // print nicely formatted xml
            try {
                XDocument doc = XDocument.Parse(xml);
                return doc.ToString();
            }
            catch (Exception) {
                return xml;
            }
        }
    } // end class
} // end namespace
Ergin
  • 9,254
  • 1
  • 19
  • 28
  • I added the code of my first attempt at implementing this to my question above (returns an error). Am I delimiting the stuff within the --AAAs correctly (with linefeeds) ? Do I need to encode the file bytes in some manner ? What are the various "documentId" fields in your example for, do they need to be unique ? Will this approach just add-to the envelope or wipe out my previous documents ? – user3757872 Oct 06 '14 at 13:12
  • At this point I've tried dozens/hundreds of variations on syntax and approach without success. Updated question with more examples. The Docusign documentation is severely lacking in this regard, the API browser gives no example of the PUT request, and I've found at least 4 different syntax/methods elsewhere in the documentation and on SO posts. Has anyone ever gotten this to work ? – user3757872 Oct 17 '14 at 16:03
  • Just modified my post and added a full code sample, please test and accept answer if it works for you. Thx, -Ergin – Ergin Oct 21 '14 at 18:26
  • Thanks Ergin, this might be getting me closer. Does it have to be a PDF ? I'm running your code with a PNG file (setting the content type appropriately) and getting:UNSPECIFIED_ERRORPDF validation failed – user3757872 Oct 21 '14 at 20:17
  • Also, the envelope seems to need to be in "created" status when running this. That's ok, except that I can't seem to change it to "sent" see: http://stackoverflow.com/questions/26412132/receiving-invalid-email-address-for-recipient-when-updating-envelope-status?noredirect=1#comment41473816_26412132 – user3757872 Oct 21 '14 at 20:22
  • It works as long as the attachment is a PDF. I was able to convert my image attachments to PDF and add them using this method, then see them in the DocuSign UI when viewing the draft envelope. Thanks Ergin !! – user3757872 Oct 21 '14 at 21:14
  • I put a comment in the code saying default content type is PDF. You can use other document types, you just need to set the Content-Type header correctly. Just change the line `string contentType = "application/pdf";` – Ergin Oct 23 '14 at 19:45
  • If you read me earlier comment (4 up in the chain), you'll see that I did set the content type to image/png for a png file, but received "PDF validation failed", same with image/jpeg for a jpg file. Converting the images to pdf and using application/pdf does work though. – user3757872 Oct 24 '14 at 20:28