Delete up to 50 comments in one request. Each row goes through the same path as a single Delete a comment, so delete guards still apply per row.
200 does not mean every row succeeded
By default this endpoint reports partial success: it answers 200 whenever the batch was processed, even if some rows failed. Read failed and the per-row results rather than branching on the status code alone.
If you want all-or-nothing instead, send all_or_none: true and treat 409 as the failure signal.
The ids of the comments to delete. Between 1 and 50 per call. A value that isn't a UUID is a 400 for the whole envelope, not a failed row.
all_or_none:optionalboolean
Defaults to false. Leave it off for partial success: each row runs in its own savepoint, successful rows commit, and you read the breakdown.
Set it to true to make the batch atomic. Every row is still evaluated, but if any row fails the whole batch is discarded and the call answers 409 instead of the 200 envelope. Side effects scheduled by the rows follow the same outcome.
{ "type": "conflict", "code": "conflict", "detail": "No rows were written because all_or_none was set and at least one row failed.", "errors": [ { "field": "ids.1", "code": "not_found", "message": "No comment matches the given query." } ]}
results — one row per input row, in request order, each carrying its index
succeeded / failed — counts, so you can branch without walking the array
A succeeded row is { index, result, id } where result is deleted. A failed row keeps the same members as a top-level error body — type, code, detail and, on validation failures, errors[] — plus index and result: "failed". So the same error-handling code works per row as at the top level. See Errors.
With all_or_none: true, every row is still evaluated — you get the full picture of what would have failed, not just the first problem — and then the batch is rolled back. The 409 body's errors[] entries are prefixed with the row index, as ids.<index>, so you can map each complaint back to the row that caused it.
Because the rollback covers the whole batch, side effects scheduled on commit — activity feed entries, webhooks — are discarded with it.
Bulk delete comments
Delete up to 50 comments in one request. Each row goes through the same path as a single Delete a comment, so delete guards still apply per row.
200does not mean every row succeededBy default this endpoint reports partial success: it answers
200whenever the batch was processed, even if some rows failed. Readfailedand the per-rowresultsrather than branching on the status code alone.If you want all-or-nothing instead, send
all_or_none: trueand treat409as the failure signal.Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs — in
https://app.plane.so/my-team/projects/, the slug ismy-team.project_id:requiredstring (uuid)The project the comments belong to. Accepts the project UUID or its bare identifier, for example
ENG.work_item_id:requiredstring (uuid)The work item the comments hang off. Accepts the work item UUID or its
PROJ-123identifier.Body Parameters
ids:requiredarray of string (uuid)The ids of the comments to delete. Between 1 and 50 per call. A value that isn't a UUID is a
400for the whole envelope, not a failed row.all_or_none:optionalbooleanDefaults to
false. Leave it off for partial success: each row runs in its own savepoint, successful rows commit, and you read the breakdown.Set it to
trueto make the batch atomic. Every row is still evaluated, but if any row fails the whole batch is discarded and the call answers409instead of the200envelope. Side effects scheduled by the rows follow the same outcome.Scopes
projects.work_items.comments:writeErrors
400invalid_requestidsmissing or empty, more than 50 rows, or a non-UUID id.401unauthorized403forbidden404not_found406not_acceptableAcceptheader asks for a representation the API can't produce.409conflictall_or_none: truebatch had at least one failing row, so the whole batch was discarded.413payload_too_large415unsupported_media_typeids.429rate_limitedRetry-Afterheader before retrying.Reading the response
The
200envelope has three members:results— one row per input row, in request order, each carrying itsindexsucceeded/failed— counts, so you can branch without walking the arrayA succeeded row is
{ index, result, id }whereresultisdeleted. A failed row keeps the same members as a top-level error body —type,code,detailand, on validation failures,errors[]— plusindexandresult: "failed". So the same error-handling code works per row as at the top level. See Errors.Atomic batches
With
all_or_none: true, every row is still evaluated — you get the full picture of what would have failed, not just the first problem — and then the batch is rolled back. The409body'serrors[]entries are prefixed with the row index, asids.<index>, so you can map each complaint back to the row that caused it.Because the rollback covers the whole batch, side effects scheduled on commit — activity feed entries, webhooks — are discarded with it.
Limits and related routes
400for the whole envelope.ids, so these routes reject it with415rather than silently misreading the payload.?fields=does not apply here — the response is a per-row result envelope, not a comment body. See Sparse fields.