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.
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Default 25, maximum 100. |
cursor | string | The next_cursor from the previous page. Unlike the change feed, this list pages with a cursor. |
curl "https://tahoe.workonward.com/api/partner/v1/erasures?limit=100" \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"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
| Field | Meaning |
|---|---|
id | The notice handle (ers_). Record it once you have acted, so you never act twice. |
action | suppression_applied or erasure_completed. See below. |
reason | Why the notice was issued. See the list of reasons below. |
workspace_id | The workspace it came from, or null when it applies to everyone. |
resources | The records to delete, each with object, id and workspace_id. |
action_required | cease_processing_and_delete_your_copy or confirm_deletion. |
deadline_at | Seven days after the notice. Your copy must be gone by then. |
destruction_pending | true while Tahoe itself must still keep the record under a legal retention rule. |
destruction_reason | Which retention rule applies, when destruction_pending is true. |
at | When the notice was issued. |
subject_identified | Always 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
| action | What it means | What you do |
|---|---|---|
suppression_applied | Tahoe has stopped processing this person. | Act now. Delete your copy of every listed record by deadline_at, and stop processing the person. |
erasure_completed | Tahoe 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
| reason | Where it came from |
|---|---|
data_subject_erasure | The person asked to be erased. |
suppression | The person asked not to be processed, short of full erasure. |
provider_takedown | A source or platform required removal. |
user_deleted | Someone in the workspace deleted the record. |
workspace_deleted | The whole workspace was deleted. |
retention_expiry | A retention period ran out. |
legal_hold_release | A 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.
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:
returnKeep 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.