Attach files to tasks and drive folders by URL or by direct-to-cloud upload.
Files in Truss live in one of two places: attached to a task on a project, or in a drive folder. The API uploads to both the same way, and gives you two options for getting the bytes to Truss.
- Give Truss a URL. If the file is already reachable at a public URL, pass the URL and Truss downloads it. This is one request.
- Upload directly to cloud storage. Ask Truss for a signed upload URL, send the bytes there, then tell Truss to attach the result. This is three requests, but the file never passes through your server twice, and it works for files that aren't publicly hosted.
Both end with the same call: add_files_to_task for a task, or add_files_to_folder for a drive folder. Each accepts a list, so you can attach several files in one request.
Attaching a file by URL
The URL must be reachable from the public internet without authentication. Truss follows up to three redirects and gives up after 30 seconds.
curl -X POST https://app2.gettruss.io/api/v1/projects/48213/tasks/9120/add_files_to_task \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"filename": "2025-W2-Baggins.pdf",
"content_type": "application/pdf",
"url": "https://files.rivendell-tax.test/exports/2025-W2-Baggins.pdf"
}
]
}'
filename — required
The name to store the file under. The extension is required and is used to decide whether the file type is allowed.
content_type — required
The file's MIME type, such as application/pdf or image/jpeg.
url — required unless signed_id is given
A publicly accessible URL. Truss downloads the file from here.
The response is the task, including attached_files with the new file and a one-hour download_url.
Uploading directly to cloud storage
Step 1: Request a signed upload URL
Describe the file to Truss. The checksum is the MD5 digest of the file's bytes, Base64-encoded, and must match what you upload in step 2.
curl -X POST https://app2.gettruss.io/api/v1/projects/48213/tasks/direct_upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"blob": {
"filename": "2025-W2-Baggins.pdf",
"content_type": "application/pdf",
"byte_size": 184322,
"checksum": "nYXrj5mT5pP2nL0C4D5N0w=="
}
}'
For a drive folder, call POST /drive_folders/direct_upload with the same body.
Truss responds with where to send the bytes and a signed_id that identifies the pending file.
{
"signed_id": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJsb2Igc2lnbmVkIn19...",
"direct_upload": {
"url": "https://truss-uploads.s3.amazonaws.com/...",
"headers": {
"Content-Type": "application/pdf",
"Content-MD5": "nYXrj5mT5pP2nL0C4D5N0w=="
}
}
}
Step 2: Upload the bytes
PUT the raw file to direct_upload.url, sending every header from direct_upload.headers exactly as returned. Don't add your Truss API key to this request; the URL is pre-signed and goes to cloud storage, not to Truss.
curl -X PUT "https://truss-uploads.s3.amazonaws.com/..." \
-H "Content-Type: application/pdf" \
-H "Content-MD5: nYXrj5mT5pP2nL0C4D5N0w==" \
--data-binary @2025-W2-Baggins.pdf
A 200 from storage means the bytes are in place. If the checksum or size doesn't match what you declared in step 1, storage rejects the upload and nothing is attached.
Step 3: Attach the file
Pass the signed_id in place of a URL.
curl -X POST https://app2.gettruss.io/api/v1/projects/48213/tasks/9120/add_files_to_task \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"filename": "2025-W2-Baggins.pdf",
"content_type": "application/pdf",
"signed_id": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJsb2Igc2lnbmVkIn19..."
}
]
}'
Each file needs its own signed id. Repeat steps 1 and 2 for every file, then attach them together or one at a time.
Uploading to a drive folder
Drive folders take the same files list and one extra field.
curl -X POST https://app2.gettruss.io/api/v1/drive_folders/3310/add_files_to_folder \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_visible": false,
"files": [
{
"filename": "2024-Form-1040-Baggins.pdf",
"content_type": "application/pdf",
"url": "https://files.rivendell-tax.test/returns/2024-Form-1040-Baggins.pdf"
}
]
}'
client_visible — required
Whether clients can see the file in their portal. Internal files are false.
The drive has to be turned on for your firm before any /drive_folders endpoint works; if it isn't, every call returns 400 with a message asking you to contact support. The firm's root folder cannot hold files; upload into a sub-folder. Create one with POST /drive_folders if you need to. The response is the folder with its files, each carrying a one-hour temporary_download_url.
What happens after an upload
Attaching a file to a task sends the file.created webhook, with a download link that stays valid for two days. Truss also runs OCR on uploaded documents; once it finishes, GET /files/{attachment_id}/text_extraction returns the extracted text, and until then text is null.
Files attached through the API count as firm uploads. They appear in attached_files with uploaded_by set to the firm, separate from what the client uploads through their portal, and they don't change the task's status.
Restrictions
- Empty files are rejected with
422. - Executable, HTML, JavaScript, and CSS files are rejected with
422, based on the filename's extension. - Download URLs in API responses are temporary. Fetch them when you need them rather than storing them.