Forget versus delete
Two different operations, two different guarantees. Choosing the wrong one is either a compliance problem or a data-loss problem, so this guide is mostly about the distinction.
Both require the delete operation on your grant — not write. Hiding a fact
is destructive from a reader's point of view.
Every response below was captured from a real run.
The distinction
forget |
delete |
|
|---|---|---|
| Intent | stop recalling this | erase this |
| Rows | preserved | purged |
| Status | 204 |
202 with a progress id |
| Reversible | no endpoint for it in v0.1 | no |
| Use when | a fact is stale or unwanted in recall | a subject asks for erasure |
Forget is suppression. Delete is erasure. If someone exercises a right to
be forgotten, forget is the wrong call — the content is still in the
database.
Forget
curl -X POST "$TETHER/v1/anchors/$ID/forget" \
-H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: your-unique-key' \
-d '{"space":"default"}'
HTTP 204
Afterwards the anchor is gone from reads:
GET /v1/anchors/$ID → HTTP 404
One nuance that surprises people. Forget applies to the current version, not the whole chain. A superseded earlier version stays readable by direct id:
GET /v1/anchors/$OLDER_VERSION → HTTP 200
That is deliberate — forget removes a fact from recall while preserving
history — but it means forget alone does not make a chain's content
unreachable. If that is what you need, use delete.
Delete
curl -X DELETE "$TETHER/v1/anchors/$ID" \
-H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: your-unique-key' \
-d '{"space":"default"}'
{
"deletionId": "del_01M2Y02ED979FQG90FQY8J3R2F",
"state": "pending",
"stages": { "d1": "done", "vectors": "pending" }
}
202, not 204. This is not evasion. Deletion spans two systems: rows in
D1 and vectors in the search index. The D1 half is synchronous and already
done by the time you get this response. The vector half is asynchronous, and
claiming otherwise would be a compliance statement Tether cannot support.
Delete targets the entire chain, not one version.
Progress
curl "$TETHER/v1/deletions/$DELETION_ID?space=default" -H "Authorization: Bearer $KEY"
{
"id": "del_01M2Y02ED979FQG90FQY8J3R2F",
"state": "pending",
"stages": { "d1": "done", "vectors": "pending" },
"createdAt": 1789860592041,
"completedAt": null
}
Poll until state is done and stages.vectors is done. Only then is the
content gone from both systems. completedAt is null until it is.
Backup expiry is reported separately and is not part of this response.
The marker, and why deletion sticks
Deletion writes a deletion_marker keyed on the chain, and the marker
outlives the rows. Three things consult it:
- Writes. A database trigger refuses any insert into a marked chain, so a correction cannot resurrect erased content.
- Reads. A marked chain is unreadable whatever state its rows are in.
stateis a property of a row, and a restore from backup brings a row back exactly as it was — so a guard that only readstatewould not be a guard. - Indexing. The background drain refuses to index an anchor whose chain is marked, so an indexing job queued before the delete cannot put the content back into the search index afterwards.
That last one is the subtle case, and it is the reason deletion is safe despite being asynchronous.
Idempotency
Both operations require an Idempotency-Key. Two copies of one delete
request produce one deletion job with one id — never two ids reporting on
the same work.