Skip to content

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.

Images must be staged before creating or patching a comment.

POST /api/v1/comments:stage-attachment

This 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).

Include the staged refs in the attachments array of the create payload:

POST /api/v1/defects/{defectUlid}/comments
POST /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>.webp
attachments/<orgUlid>/<projectUlid>/comments/<commentUlid>/<attUlid>_thumb.webp

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.

{
"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}.

Deleting a defect or test case triggers cascade cleanup of all associated comment attachments:

  1. All comment attachment R2 objects (full-size + thumbnails) are batch-deleted.
  2. The organization storage counter for comment_attachment is decremented by the total byte count.
  3. Comment rows are deleted (which cascades to comment_attachments rows via FK).

This is handled server-side automatically. No separate cleanup call is required.

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.