SupportAPI SpecsAPI Docs
LogoDeveloper Portal

Get started

OverviewGetting StartedNotifications

Uploading and Downloading Documents

Introduction

The Actionstep API supports both uploading and downloading documents, but unlike other API endpoints, this one needs to be handled slightly different. Firstly, documents are uploaded and downloaded in 5MB chunks. For a document whose entire size is within the 5MB chunk limit only one chunk is required. Larger documents are split into one or more chunks such that no single chunk is larger than 5MB.

Within Actionstep documents can be uploaded and assigned to individual Matters. Achieving this goal with the Actionstep API is a two step process. The first step is to use the files endpoint to upload a file which returns a unique identifier for the uploaded file. The second step is to create a Matter Document which associates the uploaded file with a specific Matter.

Non-Supported File Types

Please Note: Actionstep does not support uploading files with the following extensions: com, sh, exe, bat, cmd, vbs, src, msi, jar, dll, and, tpl. Checks are made to validate the file signature against the file extension and any mismatches will be rejected.

Uploading a Document

The Request Message Body

When constructing a POST request to the files endpoint the message payload needs to be in the correct format as multi-part form data, and the media type needs to be specified as multipart/form-data. In C# this would look like the following:

var content = new MultipartFormDataContent();
content.Headers.ContentType.MediaType = "multipart/form-data";

The message body is populated with a byte array containing upto 5MB of file content. When calling the files endpoint to upload a file, you are required to specify the total number of chunks the file contains plus the sequential chunk number that tells the endpoint how to reconstruct the file within Actionstep. Each call to the files endpoint will return a status indicating the success of the individual chunk upload. Upon calling the files endpoint with the final chunk, and assuming a successfull upload operation, a status of Uploaded will be returned along with a unique file identifier.

A sample C# method for uploading a document is shown below. This code fragment is part of the Actionstep sample application that can be found in the Actionstep GitHub repository.

NB: Pay particular attention to the construction of the url for a multi-chunked file. In a multi-chunked file upload, the second and subsequent chunk uploads include the file's unique file identifier in the url obtained from the response after uploading the first chunk.

public async Task<string> UploadFileAsync(IFileListEntry fileToUpload)
{
    string fileIdentifier = string.Empty;
    string boundary = Guid.NewGuid().ToString();

    int totalChunks = (int)(fileToUpload.Size / MAX_CHUNK_SIZE);
    if (fileToUpload.Size % MAX_CHUNK_SIZE != 0)
    {
        totalChunks++;
    }

    using var ms = new MemoryStream();
    await fileToUpload.Data.CopyToAsync(ms);

    for (int i = 0; i < totalChunks; i++)
    {
        long position = (i * (long)MAX_CHUNK_SIZE);
        int toRead = (int)Math.Min(fileToUpload.Size - position, MAX_CHUNK_SIZE);
        byte[] buffer = new byte[toRead];

        ms.Position = position;
        var bytesRead = await ms.ReadAsync(buffer.AsMemory(0, toRead));

        var content = new MultipartFormDataContent
        {
            { new ByteArrayContent(buffer), "file", fileToUpload.Name }
        };
        content.Headers.ContentType.MediaType = "multipart/form-data";

        var response = await _policy.ExecuteAsync(async context =>
        {
            var urlFragment = i >= 1 ? $"/{fileIdentifier}" : "";
            var message = new HttpRequestMessage(HttpMethod.Post, $"/api/rest/files{urlFragment}?part_number={i + 1}&part_count={totalChunks}")
            {
                Content = content
            };

            message = SetMessageDefaultHeaders(message);
            return await _httpClient.SendAsync(message);
        }, ContextData);

        if (response.IsSuccessStatusCode)
        {
            var responseContent = await response.Content.ReadAsStringAsync();
            if (!String.IsNullOrEmpty(responseContent))
            {
                var data = JObject.Parse(responseContent);
                var fileDto = data["files"].ToObject<FileResponseDto>();
                fileIdentifier = fileDto.Id;
            }
        }
    }
    return fileIdentifier;
}

Creating a Matter Document

To create a matter document for an uploaded file you use the actiondocuments endpoint. The message body for this endpoint requires the unique file identifier obtained from calling the files endpoint. The format of the file property is important. Its made up from the unique file identifier followed by a semi-colon followed by the name of the uploaded file, including its extension (see example below).

POST {api_endpoint}/api/rest/actiondocuments

{
  "actiondocuments": {
	"name": "Final Settlement Details",
	"file": "NzM1MjNkZDAxOGI2Y2FiNGNmZWU0OTkzOWFjOGJhMGQzODA2NjJkOTA1M2Zh;settlement-details.pdf",
	"links": {
		"action": "64"
	}
  }
}

An example of how to use the actiondocuments endpoint is included in the Actionstep sample application.

Downloading a Document

Downloading a document will return a byte array in one or more chunks used to reconstruct the original file. To download a document you call the files endpoint and pass its unique identifier and the sequential chunk number as query string parameters.

A sample C# method for downloading a document is shown below. This code fragment is part of the Actionstep sample application that can be found in the Actionstep GitHub repository.

public async Task<byte[]> DownloadFileAsync(string internalfileIdentifier, int fileSize)
{
    int chunkSize = 5242880; // 5MB max chunk size.
    int totalChunks = (int)(fileSize / chunkSize);
    if (fileSize % chunkSize != 0)
    {
        totalChunks++;
    }

    using MemoryStream ms = new MemoryStream();

    for (int i = 0; i < totalChunks; i++)
    {
        var response = await _policy.ExecuteAsync(async context =>
        {
            var message = new HttpRequestMessage(HttpMethod.Get, $"/api/rest/files/{internalfileIdentifier}?part_number={i + 1}");
            message = SetMessageDefaultHeaders(message);
            return await _httpClient.SendAsync(message);
        }, ContextData);

        if (response.IsSuccessStatusCode)
        {
            var byteData = await response.Content.ReadAsByteArrayAsync();
            ms.Write(byteData, 0, byteData.Length);
        }
    }            
    return ms.ToArray();
}

Downloading a Document using Python

The following code sample shows how to download a document using Python. For brevity the code for authentication has been omitted.

import asyncio
import aiohttp

MAX_CHUNK_SIZE = 5 * 1024 * 1024 

class MyAppState:
    AccessToken = "{insert access token here}"

_appState = MyAppState()

def set_message_default_headers():
    headers = {
        "Authorization": f"Bearer {_appState.AccessToken}",
        "Content-Type": "application/vnd.api+json",
        "Accept": "application/vnd.api+json",
        "User-Agent": "SamplePythonApp"
    }
    return headers

async def download_file_async(internalfileIdentifier, fileSize):
    totalChunks = (fileSize // MAX_CHUNK_SIZE)
    if fileSize % MAX_CHUNK_SIZE != 0:
        totalChunks += 1

    async with aiohttp.ClientSession() as session:
        data = bytearray()
        for i in range(totalChunks):
            url = f"https://ap-southeast-2.actionstep.com/api/rest/files/{internalfileIdentifier}?part_number={i + 1}"
            headers = set_message_default_headers()
            async with session.get(url, headers=headers) as response:
                print(f"Requesting part {i + 1}, URL: {url}")  # Debugging statement
                if response.status == 200:
                    chunk = await response.read()
                    print(f"Downloaded chunk {i + 1} of size {len(chunk)} bytes")  # Debugging statement
                    data.extend(chunk)
                else:
                    print(f"Failed to download part {i + 1}, Status: {response.status}")  # Debugging statement

        print(f"Total downloaded data size: {len(data)} bytes")  # Debugging statement
        return bytes(data)
    
async def save_file(file_name, data):
    with open(file_name, 'wb') as file:
        file.write(data)

async def main():
    # Replace with the required file identifier from the actiondocuments endpoint
    internalfileIdentifier = "DL::Actions::1570803::12537;"  

     # Replace with the required file's size from the actiondocuments endpoint
    fileSize = 11776 

    data = await download_file_async(internalfileIdentifier, fileSize)
    await save_file("C:\\Python Dev\\sample_word_document.docx", data)  # Replace folder and file name as required

# Run the main function
if __name__ == "__main__":
    asyncio.run(main())