Upload & attach file
Attaching files to form fields via Tallyfy’s API is a two-step process:
- Upload the file - send the file to Tallyfy’s upload endpoint and get back an asset object.
- Attach to the field - use that asset object to update the task or template, linking the file to the right form field.
POST /organizations/{org_id}/file
Both /file and /assets route to the same handler, but /file is the standard endpoint for uploads.
multipart/form-data
Authorization: Bearer {your_access_token}Accept: application/jsonX-Tallyfy-Client: APIClient
Don’t set Content-Type manually - your HTTP client sets it automatically for multipart requests.
| Field | Required | Description | Example |
|---|---|---|---|
name | Yes | The file binary data (max 100MB) | (file input) |
subject_id | Yes | ID of the parent object - run_id for tasks, checklist_id for kick-off | run_abc123 |
subject_type | Yes | Run for task fields, Checklist for kick-off fields | Run |
uploaded_from | Yes | The form field’s Capture ID for tasks, or ko_field for kick-off fields | capture_id_abc123 |
source | No | How the file is provided - local or url | local |
step_id | No | Step ID within the template (only for task uploads) | step_xyz789 |
checklist_id | No | Template ID the process was launched from (only for task uploads) | template_efg456 |
system | No | External storage system if applicable | Dropbox, Google Drive, etc. |
The API accepts these extensions: xlsx, xls, pptx, docx, ppt, doc, rtf, pdf, txt, mpga, mov, qt, mp4, webm, jpg, jpeg, gif, png, ai, psd, zip, xml, xps, dwg, csv, tif, bmp.
const accessToken = 'YOUR_PERSONAL_ACCESS_TOKEN';const orgId = 'YOUR_ORGANIZATION_ID';const apiUrl = `https://go.tallyfy.com/api/organizations/${orgId}/file`;
// --- For task field ---const formFieldId = 'CAPTURE_ID_OF_FILE_FIELD';const runId = 'PROCESS_RUN_ID';const stepId = 'STEP_ID_CONTAINING_FIELD';const checklistId = 'TEMPLATE_ID_OF_RUN';
// --- OR for kick-off field ---// const checklistIdForKO = 'TEMPLATE_ID_WITH_KICKOFF';
const fileInput = document.getElementById('yourFileInputId');if (!fileInput?.files?.length) { throw new Error('No file selected.');}const file = fileInput.files[0];
const formData = new FormData();formData.append('name', file, file.name);formData.append('source', 'local');
// For task field:formData.append('uploaded_from', formFieldId);formData.append('subject_type', 'Run');formData.append('subject_id', runId);formData.append('step_id', stepId);formData.append('checklist_id', checklistId);
// For kick-off field:// formData.append('uploaded_from', 'ko_field');// formData.append('subject_type', 'Checklist');// formData.append('subject_id', checklistIdForKO);
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', 'X-Tallyfy-Client': 'APIClient' }, body: formData});
if (!response.ok) { const errData = await response.json().catch(() => response.text()); console.error('Upload failed:', errData); throw new Error(`HTTP error! status: ${response.status}`);}
const result = await response.json();console.log('Uploaded file asset:', JSON.stringify(result, null, 2));// Use result.data for Step 2import requestsimport jsonimport os
access_token = os.environ.get('TALLYFY_ACCESS_TOKEN', 'YOUR_PERSONAL_ACCESS_TOKEN')org_id = os.environ.get('TALLYFY_ORG_ID', 'YOUR_ORGANIZATION_ID')api_url = f'https://go.tallyfy.com/api/organizations/{org_id}/file'
# --- For task field ---form_field_id = 'CAPTURE_ID_OF_FILE_FIELD'run_id = 'PROCESS_RUN_ID'step_id = 'STEP_ID_CONTAINING_FIELD'checklist_id = 'TEMPLATE_ID_OF_RUN'
# --- OR for kick-off field ---# checklist_id_for_ko = 'TEMPLATE_ID_WITH_KICKOFF'
file_path = '/path/to/your/local/document.pdf'
headers = { 'Authorization': f'Bearer {access_token}', 'Accept': 'application/json', 'X-Tallyfy-Client': 'APIClient'}
# For task field:form_data = { 'uploaded_from': (None, form_field_id), 'subject_type': (None, 'Run'), 'subject_id': (None, run_id), 'step_id': (None, step_id), 'checklist_id': (None, checklist_id), 'source': (None, 'local')}
# For kick-off field:# form_data = {# 'uploaded_from': (None, 'ko_field'),# 'subject_type': (None, 'Checklist'),# 'subject_id': (None, checklist_id_for_ko),# 'source': (None, 'local')# }
response = Nonetry: with open(file_path, 'rb') as f: files = {'name': (os.path.basename(file_path), f)} response = requests.post(api_url, headers=headers, data=form_data, files=files)
response.raise_for_status() asset_data = response.json() print('Uploaded file asset:') print(json.dumps(asset_data, indent=4)) # Use asset_data['data'] for Step 2
except FileNotFoundError: print(f'Error: File not found at {file_path}')except requests.exceptions.RequestException as e: print(f'Request failed: {e}') if response is not None: print(f'Status: {response.status_code}, Body: {response.text}')import org.apache.hc.client5.http.classic.methods.HttpPost;import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;import org.apache.hc.client5.http.entity.mime.FileBody;import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.core5.http.HttpEntity;import org.apache.hc.core5.http.io.entity.EntityUtils;import org.apache.hc.core5.http.ContentType;
import java.io.File;import java.io.IOException;
public class UploadFileApache { public static void main(String[] args) { String accessToken = System.getenv().getOrDefault("TALLYFY_ACCESS_TOKEN", "YOUR_PERSONAL_ACCESS_TOKEN"); String orgId = System.getenv().getOrDefault("TALLYFY_ORG_ID", "YOUR_ORGANIZATION_ID"); String apiUrl = "https://go.tallyfy.com/api/organizations/" + orgId + "/file"; File fileToUpload = new File("/path/to/your/document.pdf");
String formFieldId = "CAPTURE_ID_OF_FILE_FIELD"; String runId = "PROCESS_RUN_ID"; String stepId = "STEP_ID_CONTAINING_FIELD"; String checklistId = "TEMPLATE_ID_OF_RUN";
if (!fileToUpload.exists()) { System.err.println("File not found: " + fileToUpload.getPath()); return; }
try (CloseableHttpClient client = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(apiUrl); httpPost.setHeader("Authorization", "Bearer " + accessToken); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("X-Tallyfy-Client", "APIClient");
MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addPart("name", new FileBody(fileToUpload)); builder.addTextBody("source", "local", ContentType.TEXT_PLAIN); builder.addTextBody("uploaded_from", formFieldId, ContentType.TEXT_PLAIN); builder.addTextBody("subject_type", "Run", ContentType.TEXT_PLAIN); builder.addTextBody("subject_id", runId, ContentType.TEXT_PLAIN); builder.addTextBody("step_id", stepId, ContentType.TEXT_PLAIN); builder.addTextBody("checklist_id", checklistId, ContentType.TEXT_PLAIN);
httpPost.setEntity(builder.build());
client.execute(httpPost, response -> { int status = response.getCode(); HttpEntity entity = response.getEntity(); String body = entity != null ? EntityUtils.toString(entity) : null; if (status >= 200 && status < 300) { System.out.println("Upload successful:"); System.out.println(body); // Parse body JSON to get asset object for Step 2 } else { System.err.println("Upload failed. Status: " + status); System.err.println("Response: " + body); } return body; });
} catch (IOException e) { System.err.println("Request failed: " + e.getMessage()); e.printStackTrace(); } }}package main
import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "time")
func main() { accessToken := os.Getenv("TALLYFY_ACCESS_TOKEN") if accessToken == "" { accessToken = "YOUR_PERSONAL_ACCESS_TOKEN" } orgId := os.Getenv("TALLYFY_ORG_ID") if orgId == "" { orgId = "YOUR_ORGANIZATION_ID" } apiUrl := fmt.Sprintf("https://go.tallyfy.com/api/organizations/%s/file", orgId) filePath := "/path/to/your/image.png"
formFieldId := "CAPTURE_ID_OF_FILE_FIELD" runId := "PROCESS_RUN_ID" stepId := "STEP_ID_CONTAINING_FIELD" checklistId := "TEMPLATE_ID_OF_RUN"
file, err := os.Open(filePath) if err != nil { fmt.Printf("Error opening file: %v\n", err) return } defer file.Close()
body := &bytes.Buffer{} writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("name", filepath.Base(filePath)) if err != nil { fmt.Printf("Error creating form file: %v\n", err) return } _, err = io.Copy(part, file) if err != nil { fmt.Printf("Error copying file data: %v\n", err) return }
_ = writer.WriteField("uploaded_from", formFieldId) _ = writer.WriteField("subject_type", "Run") _ = writer.WriteField("subject_id", runId) _ = writer.WriteField("step_id", stepId) _ = writer.WriteField("checklist_id", checklistId) _ = writer.WriteField("source", "local")
err = writer.Close() if err != nil { fmt.Printf("Error closing multipart writer: %v\n", err) return }
client := &http.Client{Timeout: 60 * time.Second} req, err := http.NewRequest("POST", apiUrl, body) if err != nil { fmt.Printf("Error creating request: %v\n", err) return }
req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Accept", "application/json") req.Header.Set("X-Tallyfy-Client", "APIClient") req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := client.Do(req) if err != nil { fmt.Printf("Error executing request: %v\n", err) return } defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("Error reading response: %v\n", err) return }
if resp.StatusCode != http.StatusOK { fmt.Printf("Upload failed. Status: %d\nBody: %s\n", resp.StatusCode, string(respBody)) return }
fmt.Println("Upload successful:") fmt.Println(string(respBody)) // Parse JSON to extract asset object for Step 2}using System;using System.IO;using System.Net.Http;using System.Net.Http.Headers;using System.Threading.Tasks;
public class TallyfyFileUpload{ private static readonly HttpClient client = new HttpClient();
public static async Task<string> UploadFileAsync(string filePath) { var accessToken = Environment.GetEnvironmentVariable("TALLYFY_ACCESS_TOKEN") ?? "YOUR_PERSONAL_ACCESS_TOKEN"; var orgId = Environment.GetEnvironmentVariable("TALLYFY_ORG_ID") ?? "YOUR_ORGANIZATION_ID"; var apiUrl = $"https://go.tallyfy.com/api/organizations/{orgId}/file";
string formFieldId = "CAPTURE_ID_OF_FILE_FIELD"; string runId = "PROCESS_RUN_ID"; string stepId = "STEP_ID_CONTAINING_FIELD"; string checklistId = "TEMPLATE_ID_OF_RUN";
if (!File.Exists(filePath)) { Console.WriteLine($"Error: File not found at {filePath}"); return null; }
using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Add("X-Tallyfy-Client", "APIClient");
using var form = new MultipartFormDataContent(); using var fileStream = File.OpenRead(filePath); using var streamContent = new StreamContent(fileStream); form.Add(streamContent, "name", Path.GetFileName(filePath)); form.Add(new StringContent("local"), "source"); form.Add(new StringContent(formFieldId), "uploaded_from"); form.Add(new StringContent("Run"), "subject_type"); form.Add(new StringContent(runId), "subject_id"); form.Add(new StringContent(stepId), "step_id"); form.Add(new StringContent(checklistId), "checklist_id");
request.Content = form;
try { HttpResponseMessage response = await client.SendAsync(request); string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) { Console.WriteLine("Upload successful:"); Console.WriteLine(responseBody); return responseBody; } else { Console.WriteLine($"Upload failed. Status: {response.StatusCode}"); Console.WriteLine($"Response: {responseBody}"); return null; } } catch (HttpRequestException e) { Console.WriteLine($"Request error: {e.Message}"); return null; } }}A successful upload returns 200 OK with the asset object wrapped in data. You’ll need this object for Step 2.
{ "data": { "id": "asset_id_abc123xyz", "filename": "document.pdf", "version": 1, "uploaded_from": "capture_id_abc123", "uploaded_at": "2024-01-15T10:00:00Z", "step_id": "step_id_xyz789", "source": "local", "system": null, "uploaded_to_s3": true, "subject": { "id": "run_id_abc", "type": "Run" } }}Use the asset object from Step 1 to update the task or template, linking the file to the form field.
- Task field:
PUT /organizations/{org_id}/runs/{run_id}/tasks/{task_id} - Kick-off field:
PUT /organizations/{org_id}/checklists/{checklist_id}
application/json
Authorization: Bearer {your_access_token}Accept: application/jsonX-Tallyfy-Client: APIClientContent-Type: application/json
The taskdata object uses the form field’s Capture ID as the key. The value is an array containing the asset object from Step 1.
{ "taskdata": { "CAPTURE_ID_OF_FILE_FIELD": [ { "id": "asset_id_abc123xyz", "filename": "document.pdf", "version": 1, "uploaded_from": "capture_id_abc123", "uploaded_at": "2024-01-15T10:00:00Z", "step_id": "step_id_xyz789", "source": "local", "uploaded_to_s3": true, "subject": { "id": "run_id_abc", "type": "Run" } } ] }}Update the template’s prerun array. Set the value for your file field to an array containing the asset object from Step 1.
{ "prerun": [ { "id": "PRERUN_FIELD_ID_FOR_FILE", "value": [ { "id": "asset_id_abc123xyz", "filename": "document.pdf", "version": 1, "uploaded_from": "ko_field", "uploaded_at": "2024-01-15T10:00:00Z", "source": "local", "uploaded_to_s3": true, "subject": { "id": "checklist_id_abc", "type": "Checklist" } } ] } ]}These examples assume you have the assetObject from Step 1 and the required IDs.
async function attachFileToTask(assetObject, orgId, runId, taskId, formFieldId, accessToken) { const apiUrl = `https://go.tallyfy.com/api/organizations/${orgId}/runs/${runId}/tasks/${taskId}`;
const payload = { taskdata: { [formFieldId]: [assetObject] } };
const response = await fetch(apiUrl, { method: 'PUT', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', 'X-Tallyfy-Client': 'APIClient', 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
const responseData = await response.json();
if (!response.ok) { console.error('Failed to attach file:', responseData); throw new Error(`HTTP error! status: ${response.status}`); }
console.log('File attached successfully:', JSON.stringify(responseData, null, 2));}import requestsimport json
def attach_file_to_task(org_id, run_id, task_id, form_field_id, asset_object, access_token): api_url = f'https://go.tallyfy.com/api/organizations/{org_id}/runs/{run_id}/tasks/{task_id}'
payload = { 'taskdata': { form_field_id: [asset_object] } }
headers = { 'Authorization': f'Bearer {access_token}', 'Accept': 'application/json', 'X-Tallyfy-Client': 'APIClient', 'Content-Type': 'application/json' }
response = requests.put(api_url, headers=headers, json=payload) response.raise_for_status()
updated_task = response.json() print('File attached successfully:') print(json.dumps(updated_task, indent=4)) return updated_taskimport java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.io.IOException;
public class AttachFile {
public static void attachFileToTask(String orgId, String runId, String taskId, String formFieldId, String assetJsonString, String accessToken) { String apiUrl = String.format( "https://go.tallyfy.com/api/organizations/%s/runs/%s/tasks/%s", orgId, runId, taskId);
String jsonPayload = String.format( "{\"taskdata\": {\"%s\": [%s]}}", formFieldId, assetJsonString);
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl)) .header("Authorization", "Bearer " + accessToken) .header("Accept", "application/json") .header("X-Tallyfy-Client", "APIClient") .header("Content-Type", "application/json") .PUT(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build();
try { HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { System.out.println("File attached to task " + taskId + ":"); System.out.println(response.body()); } else { System.err.println("Failed. Status: " + response.statusCode()); System.err.println("Response: " + response.body()); } } catch (IOException | InterruptedException e) { System.err.println("Request failed: " + e.getMessage()); Thread.currentThread().interrupt(); } }}package main
import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time")
func attachFileToTask(orgId, runId, taskId, formFieldId string, assetObject map[string]interface{}, accessToken string) {
apiUrl := fmt.Sprintf( "https://go.tallyfy.com/api/organizations/%s/runs/%s/tasks/%s", orgId, runId, taskId)
payload := map[string]interface{}{ "taskdata": map[string]interface{}{ formFieldId: []interface{}{assetObject}, }, }
payloadBytes, err := json.Marshal(payload) if err != nil { fmt.Printf("Error marshalling payload: %v\n", err) return }
client := &http.Client{Timeout: 15 * time.Second} req, err := http.NewRequest(http.MethodPut, apiUrl, bytes.NewBuffer(payloadBytes)) if err != nil { fmt.Printf("Error creating request: %v\n", err) return }
req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Accept", "application/json") req.Header.Set("X-Tallyfy-Client", "APIClient") req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req) if err != nil { fmt.Printf("Error executing request: %v\n", err) return } defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("Error reading response: %v\n", err) return }
if resp.StatusCode != http.StatusOK { fmt.Printf("Failed. Status: %d\nBody: %s\n", resp.StatusCode, string(respBody)) return }
fmt.Println("File attached successfully:") var prettyJSON bytes.Buffer if err := json.Indent(&prettyJSON, respBody, "", " "); err == nil { fmt.Println(prettyJSON.String()) } else { fmt.Println(string(respBody)) }}using System;using System.Net.Http;using System.Net.Http.Headers;using System.Text;using System.Text.Json.Nodes;using System.Threading.Tasks;
public class TallyfyFileAttacher{ private static readonly HttpClient client = new HttpClient();
public static async Task AttachFileToTaskAsync(string assetJsonString, string orgId, string runId, string taskId, string formFieldId, string accessToken) { var apiUrl = $"https://go.tallyfy.com/api/organizations/{orgId}/runs/{runId}/tasks/{taskId}";
var assetNode = JsonNode.Parse(assetJsonString); var payload = new JsonObject { ["taskdata"] = new JsonObject { [formFieldId] = new JsonArray { JsonNode.Parse(assetNode.ToJsonString()) } } };
using var request = new HttpRequestMessage(HttpMethod.Put, apiUrl); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Add("X-Tallyfy-Client", "APIClient"); request.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request); string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) { Console.WriteLine($"File attached to task {taskId}:"); Console.WriteLine(responseBody); } else { Console.WriteLine($"Failed. Status: {response.StatusCode}"); Console.WriteLine($"Response: {responseBody}"); } }}A successful PUT returns 200 OK with the updated task or template object. The file attachment appears under the field’s Capture ID in taskdata.
{ "data": { "taskdata": { "CAPTURE_ID_OF_FILE_FIELD": [ { "id": "asset_id_abc123xyz", "filename": "document.pdf", "version": 1, "uploaded_from": "capture_id_abc123", "uploaded_at": "2024-01-15T10:00:00Z", "step_id": "step_id_xyz789", "source": "local", "uploaded_to_s3": true, "subject": { "id": "run_id_abc", "type": "Run" } } ] } }}Postman > Task operations and automation
/dl suffix that forces a download as an attachment with the original filename preserved in the Content-Disposition header. Was this helpful?
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks