Arke
BuildEntities

Delete Entities

How entity deletion works in Arke -- soft deletion via tombstoning.

Soft Deletion

Arke doesn't permanently delete entities. Instead, entities are tombstoned -- a new version is created with a _tombstone property containing deletion metadata, while all other properties and relationships are cleared.

DELETE /entities/01KFNR849AZNBWE9DYJRZR7PSA
Authorization: Bearer <token>
Content-Type: application/json

{
  "expect_tip": "bafyreif4...",
  "reason": "Duplicate entry"
}

Request Body

FieldTypeRequiredDescription
expect_tipstringYesCurrent CID for optimistic concurrency (CAS)
reasonstringNoReason for deletion (max 500 chars)
notestringNoVersion note for audit trail

Response (200 OK)

{
  "id": "01KFNR849AZNBWE9DYJRZR7PSA",
  "cid": "bafyreig7...",
  "deleted_at": "2025-01-15T10:30:00.000Z",
  "ver": 3,
  "prev_cid": "bafyreif4..."
}

What Happens

  1. A new version is created with properties: { _tombstone: { deleted_at, deleted_by, reason?, original_ver } }
  2. All other properties are cleared
  3. All relationships are cleared
  4. The entity's type remains unchanged
  5. Previous versions remain accessible via the prev link chain
  6. The entity is removed from search indexes

Tombstone Structure

The tombstone version contains only:

{
  "properties": {
    "_tombstone": {
      "deleted_at": "2025-01-15T10:30:00.000Z",
      "deleted_by": "01KUSER123...",
      "reason": "Duplicate entry",
      "original_ver": 2
    }
  },
  "relationships": []
}

Error Responses

StatusCondition
400Entity is already deleted
404Entity not found
409CAS conflict (tip changed since you fetched it)

Permissions

Deleting an entity requires the entity:delete action, which is available to admin and owner roles in the entity's collection. For collections specifically, collection:delete permission is required.


Restoring Deleted Entities

Deleted entities can be restored via POST /entities/:id/restore. This walks the version chain to find the last non-deleted version and creates a new version with that content.

POST /entities/01KFNR849AZNBWE9DYJRZR7PSA/restore
Authorization: Bearer <token>
Content-Type: application/json

{
  "expect_tip": "bafyreig7..."
}

Request Body

FieldTypeRequiredDescription
expect_tipstringYesCurrent CID of the tombstone version
notestringNoVersion note for audit trail

Response (200 OK)

{
  "id": "01KFNR849AZNBWE9DYJRZR7PSA",
  "cid": "bafyreih8...",
  "type": "document",
  "properties": { "label": "Chapter 1. Loomings", "text": "..." },
  "relationships": [...],
  "ver": 4,
  "created_at": "2025-01-10T08:00:00.000Z",
  "ts": 1736931000000,
  "edited_by": { "user_id": "01KUSER123...", "method": "manual" },
  "prev_cid": "bafyreig7...",
  "restored_from_ver": 2
}

Error Responses

StatusCondition
400Entity is not deleted, or no restorable version found
404Entity not found
409CAS conflict

Permissions

Restoring an entity requires the entity:restore action.


Cascade Delete

For deleting an entity and all related entities, use DELETE /entities/:id/cascade. This performs a BFS traversal following specified relationship predicates, deleting all matching entities within a scoped collection.

DELETE /entities/01KFOLDER123.../cascade
Authorization: Bearer <token>
Content-Type: application/json

{
  "expect_tip": "bafyreif4...",
  "collection_id": "01KCOLLECTION...",
  "cascade_predicates": ["contains", "has_*"],
  "max_depth": 10,
  "reason": "Cleanup old project"
}

Request Body

FieldTypeRequiredDescription
expect_tipstringYesCurrent CID of the root entity
collection_idstringYesCollection to scope the cascade (permission check)
cascade_predicatesstring[]YesPredicate patterns to follow
edited_by_filterstringNoOnly delete entities edited by this actor
max_depthnumberNoMax traversal depth (default: 10, max: 20)
reasonstringNoReason for deletion
notestringNoVersion note

Predicate Patterns

  • "child" -- exact match only
  • "has_*" -- matches has_document, has_image, etc.
  • "*_copy" -- matches file_copy, document_copy, etc.
  • "*" -- matches ALL predicates

Important: The collection predicate NEVER cascades, even with "*". This protects collection structure from accidental deletion.

Response (200 OK)

{
  "root": {
    "id": "01KFOLDER123...",
    "cid": "bafyreig7...",
    "deleted_at": "2025-01-15T10:30:00.000Z",
    "ver": 5,
    "prev_cid": "bafyreif4..."
  },
  "deleted": [
    { "id": "01KFILE456...", "cid": "bafyreih8...", "type": "file", "depth": 1 },
    { "id": "01KCHUNK789...", "cid": "bafyreii9...", "type": "chunk", "depth": 2 }
  ],
  "skipped": [
    { "id": "01KOTHER...", "type": "file", "reason": "not_in_collection" }
  ],
  "summary": {
    "total_traversed": 15,
    "total_deleted": 12,
    "total_skipped": 2,
    "max_depth_reached": 3
  }
}

Skip Reasons

ReasonDescription
not_in_collectionEntity is outside the scoped collection
already_deletedEntity was already tombstoned
edited_by_mismatchEntity doesn't match edited_by_filter
cas_conflictCAS conflict after retry

Permissions

Cascade delete requires entity:delete permission on the specified collection_id. Individual entity permissions are NOT checked during cascade -- the collection permission covers all entities within it.


Transparency

Tombstoned entities remain in the version chain. Anyone with access can see:

  • That the entity was deleted
  • When it was deleted
  • Who deleted it
  • The reason (if provided)
  • All previous versions of the content

This follows Arke's principle: preserve, don't delete. Content is never silently erased.

On this page