Skip to content

API: Attachments

Attachments use a three-legged, content-addressed upload: you register the file with its SHA-256 checksum, upload the bytes to a presigned URL, then confirm. If the workspace already stores identical content, the upload leg is skipped entirely.

POST /api/workspaces/{workspaceId}/tasks/{taskId}/attachments
{
"checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"fileName": "color-sample.jpg",
"mediaType": "image/jpeg",
"sizeBytes": 182734
}

checksum is the lowercase SHA-256 hex of the file content. Returns 201:

{
"id": "019f…",
"uploadUrl": "https://…storage…?X-Amz-Signature=…"
}
  • uploadUrl is null when the workspace already stores this content — the attachment is complete, skip steps 2 and 3.
  • Errors: 409 if this file is already attached to this task, 413 if the workspace storage limit would be exceeded.

PUT the raw file to uploadUrl with the same Content-Type you registered:

Terminal window
curl -X PUT --data-binary @color-sample.jpg \
-H "Content-Type: image/jpeg" "$UPLOAD_URL"

The URL is presigned and short-lived; no Todomino token is sent here.

POST /api/workspaces/{workspaceId}/tasks/{taskId}/attachments/{attachmentId}/complete

Returns 200 after the server verifies the stored object’s size. 400 if the upload never arrived.

GET /api/workspaces/{workspaceId}/tasks/{taskId}/attachments/{attachmentId}/download

Answers 302 Found with a short-lived presigned URL in the Location header. Follow the redirect without forwarding your Authorization header (most HTTP clients do this correctly by default).

DELETE /api/workspaces/{workspaceId}/tasks/{taskId}/attachments/{attachmentId}

Returns 204. Removes the attachment from the task; the stored file is deleted only when no other task references the same content.