curl --request POST \
--url https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <x-api-key>' \
--data '
{
"filename": "report.pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl",
"content_type": "application/pdf"
}
'import requests
url = "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads"
payload = {
"filename": "report.pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl",
"content_type": "application/pdf"
}
headers = {
"X-Api-Key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Api-Key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filename: 'report.pdf',
byte_size: 20481,
checksum: 'Y2hlY2tzdW1leGFtcGxl',
content_type: 'application/pdf'
})
};
fetch('https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'report.pdf',
'byte_size' => 20481,
'checksum' => 'Y2hlY2tzdW1leGFtcGxl',
'content_type' => 'application/pdf'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads"
payload := strings.NewReader("{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Api-Key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads")
.header("X-Api-Key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}"
response = http.request(request)
puts response.read_body{
"signed_id": "eyJfcmFpbHMiOnsiZGF0YSI6IjEyMyJ9fQ==",
"upload_url": "https://neeto-desk.s3.amazonaws.com/3ieihu6erxcialumauhj1pqs07hr?X-Amz-Signature=...",
"upload_headers": {
"Content-Type": "application/pdf",
"Content-MD5": "Y2hlY2tzdW1leGFtcGxl"
},
"filename": "report.pdf",
"content_type": "application/pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl"
}Reserve an attachment upload
Reserves storage for a file and returns where to send its bytes, for callers that would rather not route the file through this API. Use Upload attachment instead to send the file in one request.
Three steps:
POSTthe file’sfilename,byte_sizeandchecksumhere. The response carries asigned_id, anupload_urland theupload_headersthe next step must send.PUTthe file’s bytes toupload_url, carrying every header inupload_headersunchanged. The bytes go straight to storage and never pass through this API.- Pass the
signed_idinattachmentson Create ticket or Create comment.
checksum is the base64-encoded MD5 digest of the file, which is the form
Content-MD5 takes - not the hex digest. Generate it with
openssl dgst -md5 -binary report.pdf | base64. The storage service verifies it, so a
wrong digest fails at step 2.
A reservation holds no file until step 2 completes. Attaching a signed_id whose bytes
never arrived is rejected rather than left as a broken attachment, and a reservation
that is never used is cleaned up automatically.
curl --request POST \
--url https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <x-api-key>' \
--data '
{
"filename": "report.pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl",
"content_type": "application/pdf"
}
'import requests
url = "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads"
payload = {
"filename": "report.pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl",
"content_type": "application/pdf"
}
headers = {
"X-Api-Key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Api-Key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filename: 'report.pdf',
byte_size: 20481,
checksum: 'Y2hlY2tzdW1leGFtcGxl',
content_type: 'application/pdf'
})
};
fetch('https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'report.pdf',
'byte_size' => 20481,
'checksum' => 'Y2hlY2tzdW1leGFtcGxl',
'content_type' => 'application/pdf'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads"
payload := strings.NewReader("{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Api-Key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads")
.header("X-Api-Key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{your-subdomain}.neetodesk.com/api/external/v2/attachment_uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"report.pdf\",\n \"byte_size\": 20481,\n \"checksum\": \"Y2hlY2tzdW1leGFtcGxl\",\n \"content_type\": \"application/pdf\"\n}"
response = http.request(request)
puts response.read_body{
"signed_id": "eyJfcmFpbHMiOnsiZGF0YSI6IjEyMyJ9fQ==",
"upload_url": "https://neeto-desk.s3.amazonaws.com/3ieihu6erxcialumauhj1pqs07hr?X-Amz-Signature=...",
"upload_headers": {
"Content-Type": "application/pdf",
"Content-MD5": "Y2hlY2tzdW1leGFtcGxl"
},
"filename": "report.pdf",
"content_type": "application/pdf",
"byte_size": 20481,
"checksum": "Y2hlY2tzdW1leGFtcGxl"
}{your-subdomain} with your workspace’s subdomain. Learn how to find your subdomain in Workspace subdomain.
Sending the file
The response tells you where to send the bytes. Send them with aPUT, carrying every header
from upload_headers unchanged.
FILE=$HOME/Downloads/report.pdf
# 1. Reserve the upload. `wc -c` pads its output on BSD and macOS, so trim it.
curl -X POST "https://<subdomain>.neetodesk.com/api/external/v2/attachment_uploads" \
-H "X-Api-Key: <your_api_key>" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"$(basename "$FILE")\",
\"byte_size\": $(wc -c < "$FILE" | tr -d ' '),
\"checksum\": \"$(openssl dgst -md5 -binary "$FILE" | base64)\",
\"content_type\": \"application/pdf\"
}"
# 2. Send the bytes to the upload_url from the response. Pass each header from
# upload_headers exactly as the response gave it, values included.
curl -X PUT "<upload_url>" \
-H "Content-Type: <upload_headers.Content-Type>" \
-H "Content-MD5: <upload_headers.Content-MD5>" \
--data-binary "@$FILE"
# 3. Attach it.
curl -X POST "https://<subdomain>.neetodesk.com/api/external/v2/tickets" \
-H "X-Api-Key: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com",
"subject": "Printer is jammed",
"description": "Report attached.",
"attachments": ["<signed_id>"]
}'
checksum is the base64-encoded MD5 digest, not the hex digest. The storage service
verifies it, so a hex digest fails at step 2 rather than at step 1.upload_headers rather than composing your own. Their values are
covered by the upload signature, so an altered or missing one is refused. Which headers
appear depends on how the workspace stores files, so read them from the response rather
than hardcoding the set above.Headers
Use the X-Api-Key header to provide your workspace API key. Refer to Authentication for more information.
Body
Name the file should carry in the workspace.
"report.pdf"
Size of the file in bytes.
20481
Base64-encoded MD5 digest of the file, which is the form Content-MD5 takes.
Not the hex digest. Generate it with
openssl dgst -md5 -binary report.pdf | base64.
"Y2hlY2tzdW1leGFtcGxl"
Media type of the file. Inferred from the filename when omitted.
"application/pdf"
Response
Created - Upload reserved successfully
Pass this to attachments on ticket or comment create once the file's bytes have
been sent to upload_url. It is only accepted in the workspace that reserved it,
and attaching it before the bytes arrive is rejected.
"eyJfcmFpbHMiOnsiZGF0YSI6IjEyMyJ9fQ=="
Send the file's bytes here with an HTTP PUT. The URL is short-lived.
"https://neeto-desk.s3.amazonaws.com/3ieihu6erxcialumauhj1pqs07hr?X-Amz-Signature=..."
Headers the PUT must carry, exactly as given. Send them unchanged - the
signature covers them, so an altered or missing header is refused by the storage
service.
Hide child attributes
Hide child attributes
{
"Content-Type": "application/pdf",
"Content-MD5": "Y2hlY2tzdW1leGFtcGxl"
}
"report.pdf"
"application/pdf"
20481
"Y2hlY2tzdW1leGFtcGxl"