A DELETE does one of three things, depending on the resource. Check which before you delete anything you may want back.
In the examples, <your-url> is the address you sign in to plnnrly on, such as acme.plnnrly.com, or your organisation's own domain.
Three behaviours
- Archived, can be restored. The record stays, with a
deleted_attimestamp, and drops out of lists and reads.POST …/actions/restorebrings it back. - Archived, cannot be restored. The record stays, with a
deleted_attimestamp, but it has no restore action. These are lines inside another record, such as the rows of a sales invoice. To get one back, create it again. - Deleted permanently. The record is gone, and the API has no way to bring it back.
Which resource behaves how, taken from the reference:
- Archived, can be restored 8 resources
/customer-subscriptionsPOSTRestore/customersPOSTRestore/drive-itemsPOSTRestore/eventsPOSTRestore/projectsAccepts force=truePOSTRestore/sales-invoicesPOSTRestore/subscription-productsPOSTRestore/subscription-typesPOSTRestore- Archived, cannot be restored 3 resources
/sales-invoice-rows/subscription-items/subscription-type-items- Deleted permanently 30 resources
/absences/budget-groups/budgets/checkins/comments/company-branches/contact-roles/contacts/customer-categories/drive-documents/drive-folders/free-days/furlough-balance-mutations/holidays/labels/notes/project-links/sprints/subtasks/tasks/timers/todos/uploads/work-item-links/work-items/work-types/workflow-statuses/workflow-transitions/workflows/worklogs
Each DELETE operation page says what else happens: what is removed with the record, what is detached and what is kept. Read it before you delete in bulk.
Read archived records
Archived records are hidden until you ask for them with trashed:
trashed | On a list | On a single read |
|---|---|---|
without (the default) | Live records only. | An archived record is 404. |
with | Live and archived records. | Live or archived. |
only | Archived records only. | Archived only. A live record is 404. |
- To edit an archived record, send
trashed=withwith thePATCH. - A list inside an archived record, such as the tasks of an archived project, needs
trashed=withas well. trashedworks only where a record can be archived. Anywhere else it is400unsupported-parameter.
Restore
curl -X POST "https://<your-url>/api/v2/projects/12/actions/restore" \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json"- The answer is
200with the record in thedetailview. A restore takes notrashed: the action finds the archived record itself. - Restoring needs a token with Write for the resource, plus the permissions the operation page names.
Restoring a record that is not archived changes nothing, so a retry is safe. It answers 200, and the meta member of the response carries a warning:
{
"warnings": [
{
"code": "already_in_target_state",
"detail": "This project was not deleted."
}
]
}
A restore is not always a full undo, so read the restore operation before you rely on it. Two examples:
- Deleting a customer also archives its projects. Restoring the customer does not bring them back: restore each project.
- Deleting a sales invoice releases the hours that were invoiced on it. Restoring it brings back the invoice and its rows, but not the link to those hours, so they can be invoiced a second time.
Delete a project
DELETE/projects/{project} archives the project. Its notes, Drive items and tags stay in place, so a restore brings it back intact.
force=true deletes the project permanently. Its notes are deleted, its Drive items are archived and its tags are detached. A project that is already archived can be deleted permanently too.
curl -X DELETE "https://<your-url>/api/v2/projects/12?force=true" \
-H "Authorization: Bearer <token>" \
-H 'If-Match: "projects-12-t1791995400-3fa1"'forceis accepted on this operation only. On any otherDELETEit is400unsupported-parameter.- A project with logged time cannot be deleted at all, archived or permanently. The answer is
409state-conflict withhas_logged_time. Move or delete its worklogs first.
What can block a delete
When the record's state forbids the delete, the answer is 409 state-conflict, and conflict.reason says why:
conflict.reason | Means |
|---|---|
has_logged_time | Time has been logged against the record, such as a project or a budget. |
has_children | The record still has children, such as a work item with child work items, or a Drive folder that still holds live items or subfolders. |
locked_by_review | The worklog's hours are under review. |
locked_by_invoice | The worklog's hours have been invoiced. |
locked_by_legacy | The sales invoice predates the current invoicing flow. |
locked_by_sync | The sales invoice has been synced to the accounting system. |
Some rules answer 403 forbidden-by-policy instead, such as deleting a work type that work items still use. Each DELETE operation page lists what applies to it.
Who may delete
- Every
DELETEneeds a token with the Delete scope for that resource. Without it, the answer is403insufficient-token-scope. See Scopes and permissions. - Most deletes also need a permission from your role, and each operation page names it. A few depend on the record instead: you may delete your own worklogs and timers, for example.
- Apps signed in with OAuth cannot call a
DELETE: an OAuth token never holds the Delete scope. They can restore, because restoring needs Write. See Sign in with OAuth. - AI clients over MCP can delete only while an administrator has switched on Allow destructive actions, and only after you confirm the delete. See Permissions and safety.
Deleting twice
- Deleting an archived record again answers
204, so retrying an archive is safe. If the retry carries the sameIf-Matchand the first attempt went through, it can answer412instead: read the record withtrashed=with, and adeleted_atmeans your delete worked. - A permanent delete answers
404the second time. When you retry a delete whose response you lost, treat that404as success. - Send
If-Matchto delete only the version you looked at. See Concurrency and ETags.
Deletions and sync
A permanent delete leaves nothing to read back: a copy you keep in sync notices it only because the record is missing. Archived records stay readable with trashed=with and carry deleted_at. See Keeping a copy in sync.
Related
- Actions and state changes: how restore and the other actions behave.
- Scopes and permissions: the Delete scope.
- Keeping a copy in sync: noticing deletions.
- Errors and retries: every problem type.