Comment attachments
Comments on defects and test cases support image attachments. The flow reuses the same stage-and-commit pattern as step and defect attachments.
Staging endpoint
Section titled “Staging endpoint”Images must be staged before creating or patching a comment.
POST /api/v1/comments:stage-attachmentThis endpoint is entity-agnostic — it is not scoped to a specific defect or test case. The same staging endpoint is used for both surfaces.
Required header: X-Organization-Id: <orgUlid>
Required role: member or higher. Viewers receive 403.
Request body: multipart/form-data with one or more file parts.
Constraints:
- Accepted MIME types:
image/png,image/jpeg,image/webp - Maximum file size: see
ATTACHMENT_MAX_UPLOAD_BYTES - Maximum files per request: see
ATTACHMENT_MAX_FILES_PER_STAGE_REQUEST
201 response:
{ "attachments": [ { "ulid": "01HZATT0000000000000000001", "objectKey": "staging/<orgUlid>/...", "thumbKey": "staging/<orgUlid>/.../_thumb.webp", "mime": "image/webp", "width": 1024, "height": 768, "originalFilename": "screenshot.png" } ]}Staged objects are scoped to the requesting organization. A staged ref from another organization is rejected with 422 validation_failed when included in a create/patch payload.
Abandoned staged objects expire automatically via the R2 lifecycle rule (~48 hours).
Creating a comment with attachments
Section titled “Creating a comment with attachments”Include the staged refs in the attachments array of the create payload:
POST /api/v1/defects/{defectUlid}/commentsPOST /api/v1/test-cases/{caseUlid}/comments{ "body": "Screenshot attached for reference.", "attachments": [ { "ulid": "01HZATT0000000000000000001", "position": 0, "objectKey": "staging/<orgUlid>/...", "thumbKey": "staging/<orgUlid>/.../_thumb.webp", "mime": "image/webp" } ]}The attachments field is optional. Omitting it creates a text-only comment.
On success (201), the response comment object includes a committed attachments array with permanent keys and storage-accounted byteSize values.
Permanent keys follow the pattern:
attachments/<orgUlid>/<projectUlid>/comments/<commentUlid>/<attUlid>.webpattachments/<orgUlid>/<projectUlid>/comments/<commentUlid>/<attUlid>_thumb.webpPatching a comment’s attachments
Section titled “Patching a comment’s attachments”Pass the full desired attachment list in attachments on a PATCH. The server reconciles the diff:
- Refs present in the payload that match existing committed attachments are kept.
- New staged refs are committed (copied from staging to permanent).
- Existing committed refs absent from the payload are removed (R2 objects deleted, storage decremented).
PATCH /api/v1/defects/{defectUlid}/comments/{commentUlid}PATCH /api/v1/test-cases/{caseUlid}/comments/{commentUlid}{ "body": "Updated text.", "attachments": [{ "ulid": "01HZATT0000000000000000001", "position": 0 }]}Omitting attachments entirely leaves the existing attachment list unchanged. Passing attachments: [] removes all attachments.
Comment response shape
Section titled “Comment response shape”{ "ulid": "01HZCMT0000000000000000001", "body": "Screenshot attached for reference.", "author": { "ulid": "...", "displayName": "Ada", "avatarKey": null, "avatarVersion": 0 }, "createdAt": 1718000000000, "editedAt": null, "attachments": [ { "ulid": "01HZATT0000000000000000001", "objectKey": "attachments/<orgUlid>/<projUlid>/comments/<cmtUlid>/<attUlid>.webp", "thumbKey": "attachments/<orgUlid>/<projUlid>/comments/<cmtUlid>/<attUlid>_thumb.webp", "mime": "image/webp", "byteSize": 45678, "width": 1024, "height": 768, "originalFilename": "screenshot.png", "uploadedBy": { "ulid": "...", "displayName": "Ada", "avatarKey": null, "avatarVersion": 0 }, "url": "https://probara.net/__assets/...", "thumbUrl": "https://probara.net/__assets/.../_thumb.webp", "createdAt": 1718000000000 } ], "parentUlid": null, "pinnedAt": null, "pinnedBy": null, "mentions": [], "replyCount": 0, "replies": []}parentUlid, pinnedAt, pinnedBy, mentions, replyCount, and replies are the additive threading, pinning, and mention fields — attachments work identically on a reply or a top-level comment. See Test case comments for the full field reference.
Thumbnails are also served via the internal assets proxy at GET /__assets/{thumbKey}.
Cascade cleanup on entity delete
Section titled “Cascade cleanup on entity delete”Deleting a defect or test case triggers cascade cleanup of all associated comment attachments:
- All comment attachment R2 objects (full-size + thumbnails) are batch-deleted.
- The organization storage counter for
comment_attachmentis decremented by the total byte count. - Comment rows are deleted (which cascades to
comment_attachmentsrows via FK).
This is handled server-side automatically. No separate cleanup call is required.
Storage accounting
Section titled “Storage accounting”Comment attachments are tracked under the comment_attachment storage category in organization_storage_usage. Storage is incremented on commit and decremented on removal or cascade delete.