Attachments are files that accompany other resources provided by the Mavenlink API. Expenses can have an attachment (tracked as a receipt). Posts can also have an attachment, which will be linked at the bottom of the post on Mavenlink. The attachments endpoint allows you to create, update, or destroy the files that will be displayed alongside an Expense or a Post.

To create an Expense or Post with an Attachment, use the resulting attachment_id from the Attachment creation request in the parameters of the Expense or Post creation request.

(Note: Attachments used to be called Assets. If you run into any migration issues around this transition, please contact us!)

Creating Attachments

Attachments can be uploaded using two different methods.

Direct CDN upload:

Uploading directly to the CDN is a three step process but allows for faster uploading, significantly larger attachments, and more accurate upload progress information.

Retrieving upload credentials has two required fields, direct and attachments. The direct field must be set to true. The attachment field must include two attributes:

  • type (required) must be either post_attachment or receipt.
  • filename (required) is the filename of the file to be uploaded.

After successfully creating an attachment an upload url and credentials will be returned in JSON format with an HTTP 200 status code.

curl --form "direct=true" --form "attachment[filename]=attachment.doc" --form "attachment[type]=post_attachment" ""

The response JSON will then contain the information to upload the file:

   "id": 2,
   "action": "[upload url]",
      "utf8": "\u2713",
      "key": " ... ",
      "Content-Disposition": "attachment; filename=\"attachment.doc\"; filename*=UTF-8''attachment.doc",
      "success_action_status": 201,
      "AWSAccessKeyId": " ... ",
      "acl": "private",
      "policy": " ... ",
      "signature": " ... "

A second POST request can then be made to the provided action with the provided fields in addition to the file data.

curl --form "AWSAccessKeyId= ... " --form "attachment; filename=\"attachment.doc\"; filename*=UTF-8''attachment.doc" --form "acl=private" --form "key= ... " --form "policy= ... " --form "signature= ... " --form "success_action_status=201" --form "utf8=\u2713" --form "file=@attachment.doc" "[upload url]"

Finally, once the upload has completed successfully, a 'sync' request (specified below) is necessary to mark the upload as complete.

Below is a simplified example using ruby to achieve the same thing as with the curl commands above:

#!/usr/bin/env ruby

require 'net/https'
require "uri"
require 'json'

TOKEN="Bearer <your-secret-here>"
BOUNDARY = "AaB03x" # Make sure it is not present in the file you're uploading.

file = ARGV[0]
filename = File.basename(file)

http =, uri.port)
http.use_ssl = true
request =
request['Authorization'] = TOKEN
response = http.request(request)
first_response = JSON.parse(response.body)

# for the second request we manually build the request body
post_body = []

second_request_params = first_response["fields"].merge({ "file" => })
second_request_params.each { |k,v| 
  post_body << "--#{BOUNDARY}\r\n"
  if v.is_a?(IO)
    post_body << "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n"
    post_body << "\r\n"
    post_body <<
    post_body << "\r\n"
    post_body << "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n"
    post_body << v.to_s + "\r\n"


post_body << "--#{BOUNDARY}--\r\n"

uri = URI.parse(first_response["action"])
http =, uri.port)
http.use_ssl = true
request =
request.body = post_body.join
request["Content-Type"] = "multipart/form-data, boundary=#{BOUNDARY}"
response = http.request(request)

exit(response.code == second_request_params["success_action_status"].to_s)

Syncing a created Attachment

Syncing marks an upload as complete, and verifies its existence on the CDN.

A sync request can be made as follows:

curl -X PUT ""

For small attachments (< 10MB), you may upload to our servers and skip the 3-step direct CDN upload process. Doing so requires one field, attachment, with two required attributes:

  • type (required) must be either post_attachment or receipt.
  • data (required) is the multipart/form-data encoded file contents.

After successfully creating an attachment its metadata will be returned in JSON format with an HTTP 200 status code. To upload a file using the curl utility, you would run a command like this one:

curl --form "attachment[data]=@test.rb" --form "attachment[type]=post_attachment" ""

Destroying an existing Attachment

You can delete an Attachment as follows:

curl -X DELETE ""

The response JSON will contain a success message indicating that the attachment has been successfully deleted or an error message indicating why the post could not be deleted.