Appeal a delete you cannot undo yourself
POST /account/trash/{kind}/{id}/request-restore
Files a pending resource_request asking whoever deleted this
item to put it back (#931).
This is the OTHER half of auth.CanRestoreDeleted. That rule
gives the undo to the deleter, and to system.admin — so an
owner whose work an administrator removed sees a trash row that
says someone else deleted it and, before this endpoint, was
offered nothing further. The row was a dead end.
The capability is not an input
The server writes content.restore.request and there is no
field on this body to say otherwise. requested_capability
feeds an authorisation decision, and ADR 0064 already names the
hazard of taking it from the requester: “nothing stops a request
naming system.admin”. /assets/{id}/request-access still
ACCEPTS an explicit code for admin tooling that predates the
button; this endpoint accepts none at all, because there is no
second thing an appeal could ask for.
Who may file one
The item’s owner, and only while the item is soft-deleted. Someone who could restore it directly gets 409 rather than a request nobody needs to answer — the honest reply to “please ask them to undo it” when you may undo it yourself is “you can do that”. Any other caller gets 404: an appeal endpoint that distinguished “not yours” from “no such row” would be a probe for other people’s deleted items.
Repeating an appeal you already have pending returns 200
with the existing row, unchanged, matching
/assets/{id}/request-access. One ask is
(requester, kind, id, capability); asking twice is the same
question, and a second row would make the decider deny one of
them.
What granting it does
Performs the restore. The grant branch calls the same
softdelete primitive the per-kind restore endpoints call and
writes NO user_capability_grants row — the marker confers
nothing and there is nothing to confer, because the outcome the
requester asked for is a state change on the item, not a
standing right. See POST /admin/requests/{id}/decide.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Which soft-deletable entity id names.
Request Body
Section titled “Request Body ”object
Optional free text for the decider — why this should come back.
Responses
Section titled “ Responses ”An identical appeal was already pending. The existing request is returned unchanged; nothing was created and nobody was notified again.
A resource_request row. The state machine is pending → granted / denied (admin decision); granted → expired (CapabilitySweeper cascade). Terminal states admit no further transitions.
object
Which table target_id refers to. Access requests (#881)
are always asset; restoration appeals (#931) may name any
of the three soft-deletable kinds.
The requested object, in the table named by target_kind.
Called target_asset_id before #931, when the workflow
could only ever name an asset.
The capability code this request names. Two of them are inert markers that confer nothing and exist only to TYPE the row for its decide gate:
content.access.request— “may I see this asset” (#881). Decidable by the asset’s owner,share.grant, orsystem.admin.content.restore.request— “please undo this delete” (#931). Decidable ONLY by whoever deleted the target, or bysystem.admin.share.grantdoes not qualify: authority over sharing is not authority over moderation. Granting one performs the restore; it writes no capability grant.
True when THIS caller may decide THIS row right now,
computed by the same gate the decide endpoint applies.
Populated on the queue listings (/admin/requests,
/account/requests/incoming) and absent elsewhere.
It exists because the two queues have different gates and
the admin queue is readable by people who cannot act on
every row it contains: share.grant sees restoration
appeals but may not decide them. Rendering a decide control
from row presence alone would offer a button that 403s.
On granted requests, the linked grant’s auto-expiry. Null means permanent.
Appeal filed.
A resource_request row. The state machine is pending → granted / denied (admin decision); granted → expired (CapabilitySweeper cascade). Terminal states admit no further transitions.
object
Which table target_id refers to. Access requests (#881)
are always asset; restoration appeals (#931) may name any
of the three soft-deletable kinds.
The requested object, in the table named by target_kind.
Called target_asset_id before #931, when the workflow
could only ever name an asset.
The capability code this request names. Two of them are inert markers that confer nothing and exist only to TYPE the row for its decide gate:
content.access.request— “may I see this asset” (#881). Decidable by the asset’s owner,share.grant, orsystem.admin.content.restore.request— “please undo this delete” (#931). Decidable ONLY by whoever deleted the target, or bysystem.admin.share.grantdoes not qualify: authority over sharing is not authority over moderation. Granting one performs the restore; it writes no capability grant.
True when THIS caller may decide THIS row right now,
computed by the same gate the decide endpoint applies.
Populated on the queue listings (/admin/requests,
/account/requests/incoming) and absent elsewhere.
It exists because the two queues have different gates and
the admin queue is readable by people who cannot act on
every row it contains: share.grant sees restoration
appeals but may not decide them. Rendering a decide control
from row presence alone would offer a button that 403s.
On granted requests, the linked grant’s auto-expiry. Null means permanent.
Malformed request
object
Human-readable error summary
Example
the request could not be completedAuthentication required, missing, or invalid
object
Human-readable error summary
Example
the request could not be completedExample
{ "error": "authentication required: sign in and retry with a valid session or API token"}No such soft-deleted row of that kind, or it is not the caller’s. One response for both, deliberately.
object
Human-readable error summary
Example
the request could not be completedThe caller may already restore this item directly (they
deleted it, or they hold system.admin). Nothing to
appeal.
object
Human-readable error summary
Example
the request could not be completed