> ## Documentation Index
> Fetch the complete documentation index at: https://api.buildingswell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Archiving and deleting

> What archive does, what delete removes, and which one you want.

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.

<Warning>
  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.
</Warning>

## 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`:

```http theme={null}
PATCH /project/<id>
Content-Type: application/json

{ "isArchived": true }
```

Restore the same way:

```http theme={null}
PATCH /project/<id>
Content-Type: application/json

{ "isArchived": false }
```

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:

```http theme={null}
GET /project?isArchived=true
```

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:

```http theme={null}
GET /project?isArchived=true&isArchived=false
```

<Warning>
  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`.
</Warning>

### Archiving in bulk

`PATCH /<resource>/bulk` archives everything matching a filter, which is the safe
counterpart to the bulk delete that does not exist:

```http theme={null}
PATCH /project/bulk?costCode=WH-A
Content-Type: application/json

{ "isArchived": true }
```

Deliverables have dedicated batch endpoints, because archiving a node has to
cascade through its subtree and then re-roll the parents:

```http theme={null}
POST /deliverable/archive/batch
{ "ids": ["<uuid>", "<uuid>"] }

POST /deliverable/restore/batch
{ "ids": ["<uuid>", "<uuid>"] }
```

<Note>
  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`.
</Note>

## What each delete actually does

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

| Resource                                             | `DELETE /<resource>/{id}` does this                                                                                                                                                                                                   |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Project](/resources/project)                        | Fails with `409` in practice. Every project owns a Jobsite, and that link is `ON DELETE RESTRICT`. Archive instead.                                                                                                                   |
| [Deliverable](/resources/deliverable)                | Removes the node **and its whole subtree**. Relinks work sessions and QC entries onto the nearest surviving ancestor first. Refused with `400` when there is no ancestor and the subtree has time or QC on it.                        |
| [Stage](/resources/stage)                            | Fails with `409` whenever a team or a route step is still assigned to the station. When it does go through, deliverable stage nodes keep a `data.stageId` that no longer resolves, since that link is JSON rather than a foreign key. |
| [Work session](/resources/deliverable-work-sessions) | Removes the session and its hours. No archive exists for sessions.                                                                                                                                                                    |
| [Dependency](/resources/deliverable-dependencies)    | Removes the link and re-rolls the successor's dates. Both deliverables stay.                                                                                                                                                          |
| [Document](/resources/document)                      | Permanently removes the document. PATCH `isArchived` to retain it instead.                                                                                                                                                            |
| [QC inspection set](/resources/qc-inspection-set)    | Removes the set and the inspections under it, which is your QC record for that work.                                                                                                                                                  |
| [QC template](/resources/qc-template)                | Fails with `409` while any inspection set still references the version.                                                                                                                                                               |
| [Person](/resources/worker)                          | Always fails with `403`. People are archived, never deleted.                                                                                                                                                                          |
| [Role](/resources/role)                              | Removes the role. Users assigned to it are left without permissions.                                                                                                                                                                  |
| [User](/resources/organization-member)               | Removes the membership and cuts the person's access. Deactivate instead.                                                                                                                                                              |

### Resources added with materials and delivery

| Resource                                                                                                             | Removal behavior                                                                                             |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Items, BOM lines, production outputs, units of measure, vendors, trucks, contacts, custom properties, delivery lines | DELETE archives; PATCH `isArchived: false` restores, subject to resource constraints.                        |
| Locations                                                                                                            | DELETE archives the location and active descendants. PATCH only accepts `isArchived: false` for restoration. |
| Deliveries                                                                                                           | Use POST `/delivery/{id}/archive` or `/restore`; generic PATCH rejects archive fields.                       |
| Inventory stock and allocations                                                                                      | Generic delete/write routes are not registered. Stock has empty-row cleanup actions.                         |
| Inventory transactions                                                                                               | Append-only posting; no PATCH, upsert, or DELETE.                                                            |
| Teams, shifts, departments                                                                                           | Read-only v2 endpoints; manage in the application.                                                           |
| Item vendors and project contacts                                                                                    | DELETE removes the relationship, preserving the linked entities.                                             |

### Deleting a deliverable relinks before it removes

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:

```json theme={null}
{
  "error": {
    "message": "Cannot delete a deliverable that has work sessions and no parent deliverable to re-link them to.",
    "code": "VALIDATION"
  }
}
```

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:

```json theme={null}
{
  "error": {
    "message": "A related resource constraint was violated.",
    "code": "FK_VIOLATION"
  }
}
```

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

<Columns cols={2}>
  <Card title="Archive when" icon="box-archive">
    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.
  </Card>

  <Card title="Delete when" icon="trash">
    The record was a mistake: a duplicate order, a test project, a node created with
    the wrong type. Nothing real depends on it yet.
  </Card>
</Columns>
