Staying in sync
Erasure notices
The one feed in this API that is an instruction rather than information. If you store anything you read from Tahoe, this is the endpoint you are obliged to consume.
GET/erasuresevents:read
Every notice that applies to you, oldest first, keyset-paginated.
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Default 25, max 100. |
cursor | string | From the previous page. Unlike the change feed, this one does use a cursor. |
curl -s "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",
"action_required": "cease_processing_and_delete_your_copy",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"at": "2026-09-07T14:02:11.408Z",
"deadline_at": "2026-09-14T14:02:11.408Z",
"destruction_pending": true,
"destruction_reason": "retention_floor",
"resources": [
{
"object": "sourced_profile",
"id": "cnd_8Fj3kLm2Qd7s",
"workspace_id": "wsp_4Kd8sPm2Qx7L"
}
]
}
],
"has_more": false,
"next_cursor": null
}Two actions, and the difference is your whole obligation
| action | Means | What you must do |
|---|---|---|
suppression_applied | Tahoe has stopped processing this person. | Act now. Delete your own copy by deadline_at and stop processing them. |
erasure_completed | Tahoe has now destroyed the record. | Confirm your own deletion is done. This is the closing entry, not the instruction. |
deadline_at is seven days after the notice. That is the window you have, not a suggestion.
Why there are two events and not one
Tahoe’s own retention floor can lawfully refuse destruction for up to four years — employment records have statutory retention in several jurisdictions. So “stop processing” and “it is destroyed” are genuinely different moments, sometimes years apart.
Your obligation attaches to the suppression, not the erasure. When destruction_pending is true, Tahoe is still holding the record under a retention floor it cannot ignore, with destruction_reason saying which. That has no bearing on your deadline: you delete your copy within seven days regardless.
Reasons
| reason | Origin |
|---|---|
data_subject_erasure | The person asked. The strongest form. |
suppression | They asked not to be processed, short of full erasure. |
provider_takedown | A data provider or platform required removal. |
user_deleted | A recruiter deleted the record. |
workspace_deleted | The whole workspace was deleted. |
retention_expiry | A retention period ran out. |
legal_hold_release | A hold that was blocking deletion was released. |
Your action does not vary by reason. It is on the list so you can log why, and so a takedown is distinguishable from a person’s own request when someone asks you about it later.
A notice with no workspace obliges everyone
Acting on resources
resources names the handles to delete — the same handles you stored when you mirrored the data, so this is a direct lookup rather than a search. Match on object plus id.
def consume_erasures(session, store):
"""Honour every suppression notice. Run at least daily."""
cursor = store.load_erasure_cursor()
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
body = session.get(f"{BASE}/erasures", params=params, timeout=30).json()
for notice in body["data"]:
# The obligation attaches to the SUPPRESSION. erasure_completed is
# Tahoe confirming its own destruction, which can be years later
# because of statutory retention — waiting for it would mean holding
# the data long after the person asked us to stop.
if notice["action"] == "suppression_applied":
for resource in notice["resources"]:
store.purge(resource["object"], resource["id"])
# A pool suppression carries no workspace and still obliges us,
# so never filter this feed by workspace_id.
store.record_honoured(notice["id"], notice["deadline_at"])
if body["data"]:
cursor = body["next_cursor"]
store.save_erasure_cursor(cursor)
if not body["has_more"]:
returnThe same notices arrive as events too
data_subject.suppression_applied and data_subject.erasure_completed are on the change feed, so a consumer that already drains that feed can act without a second integration. This endpoint exists so the notices can be audited and replayed on their own, independently of your event watermark — if your event consumer has been down, this is how you catch up on the ones that matter most.
