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 returns 200 whenever it processed the batch, even if some rows failed. Check failed and the per-row results instead of relying on the status code alone.
For all-or-nothing behavior, send all_or_none: true and treat 409 as failure.
The ids of the comments to delete. Between 1 and 50 per call. A value that isn't a UUID fails the whole request with 400 instead of failing as a single row.
all_or_none:optionalboolean
Defaults to false, which gives partial success: each row runs in its own savepoint, successful rows are committed, and the response reports the outcome of each row.
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 returns 409 instead of the 200 envelope. Side effects that the rows schedule are kept or discarded along with the batch.
{ "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 entry per input row, in request order, each with its index
succeeded and failed: counts, so you can branch without walking the array
A successful row is { index, result, id }, where result is deleted. A failed row has the same members as a top-level error body (type, code, detail, and errors[] on validation failures) plus index and result: "failed", so the code that handles top-level errors also works per row. See Errors.
With all_or_none: true, every row is still evaluated, so you see every row that would have failed, not only the first, before the batch is rolled back. Each entry in the 409 body's errors[] is prefixed with the row index, as ids.<index>, so you can trace each error back to its row.
Because the rollback covers the whole batch, side effects scheduled on commit, such as activity feed entries and 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 returns
200whenever it processed the batch, even if some rows failed. Checkfailedand the per-rowresultsinstead of relying on the status code alone.For all-or-nothing behavior, send
all_or_none: trueand treat409as failure.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 belong to. 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 fails the whole request with
400instead of failing as a single row.all_or_none:optionalbooleanDefaults to
false, which gives partial success: each row runs in its own savepoint, successful rows are committed, and the response reports the outcome of each row.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 returns409instead of the200envelope. Side effects that the rows schedule are kept or discarded along with the batch.Scopes
projects.work_items.comments:writeErrors
400invalid_requestidsis missing or empty, has more than 50 rows, or contains an id that isn't a UUID.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 entry per input row, in request order, each with itsindexsucceededandfailed: counts, so you can branch without walking the arrayA successful row is
{ index, result, id }, whereresultisdeleted. A failed row has the same members as a top-level error body (type,code,detail, anderrors[]on validation failures) plusindexandresult: "failed", so the code that handles top-level errors also works per row. See Errors.Atomic batches
With
all_or_none: true, every row is still evaluated, so you see every row that would have failed, not only the first, before the batch is rolled back. Each entry in the409body'serrors[]is prefixed with the row index, asids.<index>, so you can trace each error back to its row.Because the rollback covers the whole batch, side effects scheduled on commit, such as activity feed entries and webhooks, are discarded with it.
Limits and related routes
400.ids, so they reject it with415instead of misreading the payload.?fields=doesn't apply here. The response is a per-row result envelope, not a comment body. See Sparse fields.