Skip to content

Erasure notices

When Tahoe deletes or suppresses a person, and what you must delete in return.

The erasure feed is the one part of this API that is an instruction rather than information. Each notice says that a person must no longer be processed, names the records involved, and gives you a deadline. If you store anything you read from Tahoe, you are expected to read this feed at least once a day and act on it.

List erasure notices

GET/erasuresevents:read

Every notice that applies to your key, oldest first, one page at a time.

ParameterTypeNotes
limitintegerDefault 25, maximum 100.
cursorstringThe next_cursor from the previous page. Unlike the change feed, this list pages with a cursor.
Request
curl "https://tahoe.workonward.com/api/partner/v1/erasures?limit=100" \
  -H "Authorization: Bearer $TAHOE_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "object": "erasure",
      "id": "ers_5Nx3jLm7Qd2s",
      "action": "suppression_applied",
      "reason": "data_subject_erasure",
      "workspace_id": "wsp_4Kd8sPm2Qx7L",
      "resources": [
        {
          "object": "applicant",
          "id": "apl_8Fj3kLm2Qd7s",
          "workspace_id": "wsp_4Kd8sPm2Qx7L"
        },
        {
          "object": "application",
          "id": "app_6Qm2xKd4Rp8v",
          "workspace_id": "wsp_4Kd8sPm2Qx7L"
        }
      ],
      "action_required": "cease_processing_and_delete_your_copy",
      "deadline_at": "2026-09-14T14:02:11.408Z",
      "destruction_pending": true,
      "destruction_reason": "retention_floor",
      "at": "2026-09-07T14:02:11.408Z",
      "subject_identified": false
    }
  ],
  "has_more": false,
  "next_cursor": null
}

The notice object

FieldMeaning
idThe notice handle (ers_). Record it once you have acted, so you never act twice.
actionsuppression_applied or erasure_completed. See below.
reasonWhy the notice was issued. See the list of reasons below.
workspace_idThe workspace it came from, or null when it applies to everyone.
resourcesThe records to delete, each with object, id and workspace_id.
action_requiredcease_processing_and_delete_your_copy or confirm_deletion.
deadline_atSeven days after the notice. Your copy must be gone by then.
destruction_pendingtrue while Tahoe itself must still keep the record under a legal retention rule.
destruction_reasonWhich retention rule applies, when destruction_pending is true.
atWhen the notice was issued.
subject_identifiedAlways false. A notice never names the person, only the records.

Reading it every day

A cursor expires after an hour, and the last page has no cursor at all. So there is no resume point to keep from one run to the next. Read the feed from the start on each run and skip any notice whose id you have already acted on.

Two actions

actionWhat it meansWhat you do
suppression_appliedTahoe has stopped processing this person.Act now. Delete your copy of every listed record by deadline_at, and stop processing the person.
erasure_completedTahoe has destroyed the record.Make sure your copy is gone too. If you still hold any listed record, delete it now.

Not every erasure starts with a suppression notice. When Tahoe can destroy a record straight away, the only notice you receive is erasure_completed. Treat whichever notice reaches you first as the instruction to delete.

Why there are two

Employment records have legal retention periods in several places, so Tahoe may have to keep a record for up to four years after a person asks to be erased. “Stop processing” and “the record is destroyed” can therefore be years apart.

Your deadline starts with the suppression. When destruction_pending is true, Tahoe is still holding the record under a retention rule it cannot ignore, and destruction_reason says which one. That changes nothing for you: delete your copy within seven days.

Reasons

reasonWhere it came from
data_subject_erasureThe person asked to be erased.
suppressionThe person asked not to be processed, short of full erasure.
provider_takedownA source or platform required removal.
user_deletedSomeone in the workspace deleted the record.
workspace_deletedThe whole workspace was deleted.
retention_expiryA retention period ran out.
legal_hold_releaseA legal hold that was blocking deletion was released.

What you do does not depend on the reason: you delete. The reason is there so you can log why, and so a removal required by a source can be told apart from a person’s own request if anyone asks you later. New reasons may be added, so treat an unfamiliar value the same way.

A notice with no workspace applies to everyone

Acting on the records

resources names the handles to delete. They are the same handles you stored when you copied the data, so this is a direct lookup, not a search. Match on object and id together.

An erasure reader
BASE = "https://tahoe.workonward.com/api/partner/v1"


def consume_erasures(session, store):
    """Act on every erasure notice. Run at least once a day."""
    cursor = None

    # A cursor expires after an hour and the last page has none, so there is
    # no resume point to keep between runs. Read from the start every time and
    # skip the notices you have already acted on.
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        response = session.get(f"{BASE}/erasures", params=params, timeout=30)
        response.raise_for_status()
        body = response.json()

        for notice in body["data"]:
            if store.notice_handled(notice["id"]):
                continue
            # Delete on EITHER action. suppression_applied starts the 7-day
            # clock; erasure_completed can arrive on its own when Tahoe
            # destroys a record at once. Never wait for erasure_completed.
            for resource in notice["resources"]:
                store.purge(resource["object"], resource["id"])
                store.suppress_forever(resource["object"], resource["id"])
            # A notice with workspace_id null applies to every holder of the
            # data, so never filter this feed by workspace.
            store.record_notice(notice["id"], notice["reason"], notice["deadline_at"])

        cursor = body["next_cursor"]
        if not cursor:
            return

Keep a permanent list of the handles you deleted and check it before every write, so a later sync cannot bring a deleted person back. The Deletion and erasure guide explains why that list must never expire.

The same notices on the change feed

Each notice also appears on the change feed as data_subject.suppression_applied or data_subject.erasure_completed, so a reader that already follows the feed can act without a second integration. This endpoint exists so you can audit and replay the notices on their own, apart from your event watermark. If your feed reader has been down, this is how you catch up on the notices that matter most.