SharePointVault ~/gotchas/rest-api ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ delete-is-permanent-recycle-is-the-bin.md
sharepoint-vault›gotchas›rest-api›delete-is-permanent-recycle-is-the-bin.mdhistoryedit on GitHub
● GOTCHArest-api· 2 min read

REST DELETE on a list item is permanent — recycle() is the bin

applies to: SharePoint Onlinereviewed: 2026-09-19#rest-api#lists#data-loss#retention

X-HTTP-Method: DELETE on /items(id) removes the item for good: it shows up in neither the site recycle bin nor the site-collection one. If your UI promises "it goes to the recycle bin, you can restore it", you must call POST /items(id)/recycle() instead.

X-HTTP-Method: DELETE na /items(id) maže položku natrvalo – není pak ani v koši webu, ani v koši kolekce. Když UI slibuje „jde to do koše, dá se to obnovit“, musí se volat POST /items(id)/recycle().

Symptom

A cleanup/retention feature deletes rows with the usual REST call and the UI says the rows went to the recycle bin. The admin opens the recycle bin to restore one — and it is empty. The data is gone.

What is actually going on

Two different operations that look the same from the caller's side:

POST /_api/web/lists/getbytitle('X')/items(42)
IF-MATCH: *
X-HTTP-Method: DELETE

deletes the item permanently (HTTP 200/204, nothing in either bin), while

POST /_api/web/lists/getbytitle('X')/items(42)/recycle()
Accept: application/json;odata=nometadata

moves it to the site recycle bin (HTTP 200, returns the new recycle-bin GUID) where it appears as ItemType 3 and can be restored for the tenant's retention window (93 days by default in SharePoint Online).

Measured 2026-09-19 on SharePoint Online, modern list, item created and removed in the same session; /_api/web/recyclebin and /_api/site/recyclebin were both queried right after each call.

Note that the list UI behaves differently from the API here — deleting a row in the browser recycles it, which is why the API's behaviour surprises people.

Fix

// permanent — use only when you mean it
await fetch(`${list}/items(${id})`, {
  method: 'POST',
  headers: { 'IF-MATCH': '*', 'X-HTTP-Method': 'DELETE' }
});

// recoverable — what "it goes to the recycle bin" means
const res = await fetch(`${list}/items(${id})/recycle()`, {
  method: 'POST',
  headers: { Accept: 'application/json;odata=nometadata' }
});
// res.ok → body is the recycle-bin item GUID

Verify it rather than trusting the call name:

GET /_api/web/recyclebin?$select=Title,ItemType,DeletedDate&$top=10&$orderby=DeletedDate desc

Why it bites

The wording in a UI is a promise about the data, and it is as binding as the feature itself. A retention or wipe screen that says "restorable for 93 days" over a permanent delete is worse than one that admits the delete is final — the admin makes different decisions based on it.

Two habits that catch this class of bug:

  • After a destructive feature works, open the recycle bin and look. The delete returning 200 tells you nothing about where the item went.
  • When you write a sentence about a data property (immutable, encrypted, restorable, retained for N days), find the line of code that makes it true. If there isn't one, change the sentence or write the code.

See also

Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-09-19last updated: 2026-09-24historyedit on GitHubview source