Delete vs Recycle in SharePoint with REST API: What You Need to Know


Deleting a file in SharePoint isn’t a single action — you’re choosing between sending it to the Recycle Bin (recoverable) or removing it permanently (not, without a backup). Which one you should reach for, and how to do either one through the REST API, is what this post covers.


In this post: Delete vs recycle: what’s the difference · Implementing both with the REST API · Restoring from the Recycle Bin · Recycled doesn’t mean off your storage quota · Best practices · When would you actually use this? · Related reading


Delete vs recycle: what’s the difference

Recycle moves an item to the Recycle Bin instead of removing it. It’s a two-stage safety net: items land in the user-facing first-stage bin, where anyone with edit access can restore them without IT involvement. If removed from there, they move to the second-stage (site collection) bin, which only site collection administrators can restore from or empty. Both stages share a single 93-day window from the original deletion date — moving between stages doesn’t reset the clock, and Microsoft’s own limit here isn’t configurable per tenant.

Delete (called permanently, past both recycle stages) removes the item along with its metadata, permissions, and version history. It’s not instantly unrecoverable, though — Microsoft retains SharePoint content in backup for an additional 14 days after the 93-day window closes, but getting it back at that point means a support ticket with Microsoft, not a self-service restore.


Implementing both with the REST API

Moving an item to the Recycle Bin — recoverable, the default you want in most automation:

fetch("https://yourdomain.sharepoint.com/sites/yoursite/_api/web/lists/getbytitle('Documents')/items(1)/recycle()", {
    method: "POST",
    headers: {
        "Accept": "application/json;odata=verbose",
        "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value
    }
})
.then(response => response.json())
.then(data => console.log("Item moved to Recycle Bin", data))
.catch(error => console.error("Error:", error));

Permanently deleting an item — skips the Recycle Bin entirely, use with intent:

fetch("https://yourdomain.sharepoint.com/sites/yoursite/_api/web/lists/getbytitle('Documents')/items(1)", {
    method: "DELETE",
    headers: {
        "Accept": "application/json;odata=verbose",
        "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value
    }
})
.then(() => console.log("Item permanently deleted"))
.catch(error => console.error("Error:", error));

Both calls need X-RequestDigest as CSRF protection, same as any other write against the REST API. Notice the two endpoints are almost identical — DELETE against the item’s own URL removes it outright, while POST to /recycle() is what routes it through the Recycle Bin instead.


Restoring from the Recycle Bin

The entire point of reaching for recycle() over DELETE is being able to reverse it — worth actually showing that half of the round trip. Restoring needs the item’s Recycle Bin ID first (not its original list item ID, which no longer applies once it’s in the bin), found by querying the bin and filtering by name:

// Find the recycled item by its original file name
fetch("https://yourdomain.sharepoint.com/sites/yoursite/_api/web/RecycleBin?$filter=LeafName eq 'Report.docx'", {
    headers: { "Accept": "application/json;odata=verbose" }
})
.then(response => response.json())
.then(data => {
    const recycledItem = data.d.results[0];
    if (!recycledItem) { console.error("Not found in Recycle Bin"); return; }

    // Restore it using its Recycle Bin ID
    return fetch(`https://yourdomain.sharepoint.com/sites/yoursite/_api/web/RecycleBin('${recycledItem.Id}')/restore()`, {
        method: "POST",
        headers: {
            "Accept": "application/json;odata=verbose",
            "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value
        }
    });
})
.then(() => console.log("Item restored"))
.catch(error => console.error("Error:", error));

One failure mode worth knowing before scripting this into anything automated: restore fails outright if a file with the same name already exists at the original location — a real possibility if someone re-created the file after the original was recycled. There’s no “restore and rename” option through the API; the conflicting file at the destination has to be renamed or moved first.


Recycled doesn’t mean off your storage quota

Worth knowing before “just recycle everything, it’s free” becomes the default cleanup habit: items sitting in the first-stage Recycle Bin still count against the site’s storage quota, exactly the same as if they hadn’t been recycled at all. Recycling isn’t a way to reclaim space — it’s a way to defer permanent removal while keeping a 93-day safety net. Storage is only actually freed once an item moves to the second-stage bin and gets purged from there (or the 93-day window expires and it’s purged automatically).

The second-stage bin itself is mostly the exception to that rule — items there generally don’t count against the quota-used figure. There’s one specific carve-out worth knowing if a script is deleting whole subsites rather than files or list items: a deleted subsite (a “web”) goes directly into the second-stage bin, but unlike other second-stage content, it keeps counting against storage until it’s purged from there too. If a cleanup script’s goal is actually recovering quota, not just tidying the active view, checking what’s sitting unpurged in both recycle stages matters as much as what got recycled in the first place.


Best practices
  • Default to recycle(), not DELETE — reserve the permanent call for scripts you’ve already validated, not the first draft of a cleanup script.
  • Limit delete permissions separately from edit permissions where the list holds anything sensitive — being able to edit an item shouldn’t automatically mean being able to erase it without a trace.
  • If you’re automating bulk deletions (a cleanup flow, a migration script), log what was deleted and when before the call runs, not after — if the script has a bug, you want a record of intent, not just an empty Recycle Bin.
  • Automate Recycle Bin cleanup with Power Automate or a scheduled script if storage from deleted-but-not-yet-purged items is a real concern, rather than emptying it manually.

Both stages of the Recycle Bin share one 93-day window from the original deletion — moving between them doesn’t buy more time.

When would you actually use this?
  • General cleanup, user-triggered deletions, anything where a mistake is plausible — recycle. It costs nothing but 93 days of storage and buys a safety net.
  • Regulatory or compliance requirements mandate immediate, irreversible removal of specific sensitive data — permanent delete, and document why in whatever process triggered it.
  • You’re clearing out test data or content that was never meant to persist — permanent delete is fine here too, since there’s nothing worth the 93-day hold.


That’s the walkthrough. Drop a comment if you get stuck.


App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *