Attachments
Uploading files, and the deliberate reason the API never hands you a storage URL.
Scopes: attachments:read, attachments:write
Uploading
Multipart, with the task key:
curl -X POST https://acme.krrim.com/api/v1/attachments/ \
-H "Authorization: Bearer krm_live_..." \
-F "task=krrim-0042" \
-F "file=@trace.har"Downloading
The API never returns a storage URL. The media bucket is public at bucket
level with query-string authentication off, which makes a raw
attachment.file.url a permanent, unauthenticated link to the file — one that
keeps working after the task is deleted and after the person who uploaded it
leaves.
So file is write-only. Reads get two fields instead.
| Field | Use it for |
|---|---|
download_url | Absolute. For a machine fetching the bytes now |
download_path | Relative. For anything stored in content — a description, a comment body |
The distinction matters: an absolute URL written into a task description bakes today's host into content that outlives it. Store the relative path.
GET /api/v1/attachments/{id}/download/This route enforces the same permission check as reading the task, so a link that leaks is still useless to someone outside the project.
Endpoints
| Method | Path | Scope |
|---|---|---|
GET | /api/v1/attachments/ | attachments:read |
POST | /api/v1/attachments/ | attachments:write |
GET | /api/v1/attachments/{id}/ | attachments:read |
PATCH | /api/v1/attachments/{id}/ | attachments:write |
DELETE | /api/v1/attachments/{id}/ | attachments:write |
GET | /api/v1/attachments/{id}/download/ | attachments:read |