---
title: "Creating a new Attachment"
method: POST
path: "/attachments"
tags: ["Attachments"]
---

# Creating a new Attachment

`POST /attachments`

## Request

- Base URL: `https://api.mavenlink.com/api/v1`
- URL: `https://api.mavenlink.com/api/v1/attachments`
- Auth: none declared

There are two different ways to upload file attachments.  Direct CDN upload, or Kantata OX server upload.

## 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 `attachment`.  The `direct` field must
be set to `true`.  The `attachment` field must include two attributes:

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

The file name must not contain spaces or special characters other than an underscore ("_") or a hyphen ("-"),
otherwise the file upload will fail.

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" "https://api.mavenlink.com/api/v1/attachments.json"
```

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

```
{
   "id": 2,
   "action": "[upload url]",
   "fields":{
      "utf8": "✓",
      "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 field values
returned in the JSON response, in addition to the file data. The AWSAccessKeyId field value
is only active for 5 seconds, so these consecutive calls are meant to be made in quick succession via a script.

Other than `$file`, variables in the request that start with $ are the values represented by the " ... " blanks in the JSON response.

```
curl -X POST "[upload url]" -H 'cache-control: no-cache' --form 'AWSAccessKeyId='$awsKey --form 'Content-Disposition="attachment; filename="$file"; filename*=UTF-8''''''$file"' --form 'x-amz-server-side-encryption=AES256' --form 'key='$key --form 'policy='$policy --form 'signature='$sig --form 'acl=private' --form 'success_action_status=201' --form 'utf8=✓' --form 'file=@'$file
```

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:

```ruby
#!/usr/bin/env ruby

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

TOKEN="Bearer <your-secret-here>"
ATTACHMENTS_ENDPOINT="https://api.mavenlink.com/api/v1/attachments.json"
BOUNDARY = "AaB03x" # Make sure it is not present in the file you're uploading.

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

uri = URI.parse(ATTACHMENTS_ENDPOINT)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = TOKEN
request.set_form_data(first_request_params)
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" => File.open(file) })
second_request_params.each do |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 << File.read(file)
    post_body << "\r\n"
  else
    post_body << "Content-Disposition: form-data; name="#{k}"\r\n\r\n"
    post_body << v.to_s + "\r\n"
  end
end

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

uri = URI.parse(first_response["action"])
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
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 "https://api.mavenlink.com/api/v1/attachments/2/sync.json"
```

## Kantata OX Server Upload

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" "https://api.mavenlink.com/api/v1/attachments.json"
```


This endpoint returns structured Attachment objects.
As with all Kantata OX API endpoints, the returned data will be referenced in sorted order in the `results` array
and will be indexed by ID in the `attachments` top-level JSON key.
Please see our [Response Format](#section/Response-Format) section for more information.

## Request body

- object
  - `attachment` object
    - `type` string, required: Indicates the type of Attachment within Kantata OX. Allowed values are: receipt or post_attachment. A receipt is attached to an Expense, and a post_attachment is attached to a Post.
    - `direct` boolean: Whether or not this is a direct CDN upload. If direct is set to true, the filename field is required. If direct is set to false or is not present, the data field is required.
    - `data` string: The multipart/form-data encoded file contents. Only used if direct is not set to true. Note that the maximum allowed size for file data uploads is 10MB. For larger files, use a direct CDN upload.
    - `filename` string: The filename of the file to be uploaded. Only used if direct is set to true.

## Response `200`

Attachment has been created.

- object
  - `count` integer
  - `meta` object
    - `count` integer
    - `page_count` integer
    - `page_number` integer
    - `page_size` integer
  - `results` object[]
    - `key` string
    - `id` string
  - `attachments` object

## Other responses

- `400`: Bad Request
- `401`: Unauthorized request
- `403`: Forbidden request
- `404`: Page Not Found
- `422`: Unprocessable Entity
- `503`: Service is unavailable

---

[API](https://skmtc.dev/kantata/apis/kantata-ox-api-documentation.md) - [All operations](https://skmtc.dev/kantata/apis/kantata-ox-api-documentation/llms.txt) - [OpenAPI document](https://skmtc.dev/kantata/apis/kantata-ox-api-documentation/revisions/9d4ebed2fa98?raw)
