Task Attachments
Attach files to tasks using presigned upload URLs
Task Attachments
Tasks can carry file attachments — screenshots, mockups, diagrams, logs — that give agents context alongside the text description. The claiming worker receives each one as a short-lived signed download URL.
The server does not restrict attachments by content type. Any mimeType is
accepted. Images are the intended use and the only kind the dashboard previews,
but nothing rejects other types, so treat the limit as a convention rather than
an enforced constraint.
How It Works
Attachments upload directly to S3-compatible storage (R2, S3) via presigned URLs, so the file never transits the buildd API:
- Request upload URLs —
POST /api/attachments/uploadwith file metadata - Upload directly —
PUTeach file to its returned presigned URL - Attach to a task — pass the storage keys in the
attachmentsarray onPOST /api/tasks
Step 3 is a first-class field. Putting a storage key in the task description does nothing — nothing reads keys out of prose.
Constraints
- 10MB per file.
sizeBytesis required, must be a positive integer, and is signed into the grant — a file larger than it declared will fail the upload rather than sneaking through. Over the limit returns413. - 5 files per request. Over that returns
400. - Keys are workspace-scoped. Each key is minted under the requesting
workspace's own prefix, and
POST /api/tasksrejects a key from a different workspace with400. - Presigned upload URLs last 10 minutes. Download URLs handed to workers last 1 hour. Request the upload URLs immediately before uploading; a batch flow that pauses for human input will find them expired.
API Reference
Request Upload URLs
curl -X POST https://buildd.dev/api/attachments/upload \
-H "Authorization: Bearer bld_xxx" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "<workspace-id>",
"files": [
{ "filename": "screenshot.png", "mimeType": "image/png", "sizeBytes": 245000 }
]
}'filename, mimeType and sizeBytes are all required per entry.
Response
{
"uploads": [
{
"storageKey": "attachments/<workspace-id>/<uuid>/screenshot.png",
"uploadUrl": "https://storage.example.com/presigned-url...",
"filename": "screenshot.png",
"mimeType": "image/png"
}
]
}Upload the File
curl -X PUT "https://storage.example.com/presigned-url..." \
-H "Content-Type: image/png" \
--data-binary @screenshot.pngThe Content-Type header is not optional — it must match the mimeType you
submitted, or the presigned signature will not validate.
Attach to a Task
curl -X POST https://buildd.dev/api/tasks \
-H "Authorization: Bearer bld_xxx" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "<workspace-id>",
"title": "Fix the layout in the attached screenshot",
"attachments": [
{
"storageKey": "attachments/<workspace-id>/<uuid>/screenshot.png",
"mimeType": "image/png",
"filename": "screenshot.png"
}
]
}'All three of storageKey, mimeType and filename are required per entry. An
entry missing any one of them is dropped silently — no error, and the agent
never sees the file. If an attachment does not reach a worker, check the entry
has all three keys first.
Authentication
Both API key (Bearer bld_xxx) and session auth work. The caller must have
access to the workspaceId it names; otherwise the request returns 403.
Object storage must be configured on the server — STORAGE_ENDPOINT,
STORAGE_ACCESS_KEY and STORAGE_SECRET_KEY at minimum, plus
STORAGE_BUCKET, STORAGE_REGION and STORAGE_PUBLIC_URL as needed. The
endpoint returns 503 when the first three are absent. Note that
STORAGE_BUCKET is not part of that readiness check, so a wrong bucket name
passes the gate and fails later at upload time.