Skip to main content
Almost every time you want to get rid of something, you want to archive it, not delete it. Archiving is reversible, it cascades the way you would expect, and it keeps the history that timesheets and QC records depend on. DELETE behavior depends on the resource. Some endpoints permanently remove records; materials catalogs, custom properties, trucks, contacts, and delivery lines use DELETE as an archive action. Check the table below before deleting.
A hard delete is permanent. BuildingSwell does not keep a copy, so a deleted order cannot be recovered from the API or from the app.

Archive instead

Most writable archivable resources use isArchived. Locations use DELETE to archive, and deliveries have /archive and /restore commands. Read-only organization lists do not expose archive writes. For projects, set isArchived:
Restore the same way:
Archive behavior varies by resource:
  • Resources with collision handling can give the record’s name or identifier an - (archived <timestamp>) suffix so it stops colliding with your active records, where <timestamp> is the archive time in epoch milliseconds. Restoring strips the suffix, and picks the next free variant if something has taken the original name meanwhile. Match the suffix with a pattern rather than a literal string.
  • archivedAt is stamped with the current time, and cleared again on restore, for the resources that carry the field. Each resource page lists its own fields: People and Stages both carry isArchived and archivedAt.
  • Models with an archive-aware default filter hide archived records from reads, including reads by id. Plain link/reporting models can expose an isArchived field without applying that default; their resource pages call this out.
That last point catches people out. Filters default to isArchived eq false, so ask for archived records explicitly when you want them:
This holds for archive-aware resources, People included. It does not hold for item-vendor relationships or inventory allocations: those plain models still return archived rows unless you filter them. On archive-aware resources, your isArchived replaces the default rather than narrowing it, and repeating the parameter matches either value, so one request can return both:
The default applies to single-record reads too. GET /project/{id} on an archived record returns 404, and no query parameter overrides it on that route. When an id might belong to an archived record, read it through the list endpoint: GET /project?id=<uuid>&isArchived=true&isArchived=false.

Archiving in bulk

PATCH /<resource>/bulk archives everything matching a filter, which is the safe counterpart to the bulk delete that does not exist:
Deliverables have dedicated batch endpoints, because archiving a node has to cascade through its subtree and then re-roll the parents:
Not every resource is archivable. Work sessions and QC records have no isArchived field. Documents do support archive and restore through isArchived; the Hub archived view uses archived=true.

What each delete actually does

Deletes differ per resource, and the differences matter. Some cascade, some relink first, some are refused outright.

Resources added with materials and delivery

This is the one delete with real machinery behind it, because losing hours or QC history silently would be worse than refusing the request. Before the rows go:
  1. Work sessions on any node in the subtree are moved onto the nearest surviving ancestor, so the hours stay attributed to something real.
  2. QC inspection sets are moved the same way.
  3. Dependency links touching any deleted node are removed with it.
When there is no surviving ancestor, which is what happens when you delete a root order, there is nowhere to move that history to. The call is rejected with 400 if the subtree has any work sessions or QC entries:
Archive the order instead.

Bulk delete does not exist

On writable factory resources, DELETE /<resource>/bulk returns 403. Read-only resources do not register that route. A broad filter can never wipe out a table in one call, by design. Archive them in bulk instead, or delete them one at a time on the resources where a single delete is allowed at all. People are not one of them: DELETE /worker/{id} returns 403 too.

Why a delete comes back as 409

A 409 with code: "FK_VIOLATION" means another record still points at the one you are deleting, and the database is protecting it:
The three you will actually hit are projects, which always own a Jobsite, stations that a team or route step is still assigned to, and QC template versions that inspection sets still reference. In every case the answer is to archive, or to remove the referencing records first.

Picking between them

Archive when

The work happened. You need the history for payroll, QC, or reporting, and you only want it out of the way. This is nearly always the right answer.

Delete when

The record was a mistake: a duplicate order, a test project, a node created with the wrong type. Nothing real depends on it yet.