Bring several Bin entries back at once
const url = 'https://beta-api.plune.ai/v1/bin/restore';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"items":[{"kind":"suite","id":"6f1c2b3a-0000-4000-8000-0000000000a1"},{"kind":"case","id":"6f1c2b3a-0000-4000-8000-0000000000c1"},{"kind":"run","id":"6f1c2b3a-0000-4000-8000-0000000000d1"}],"dryRun":true}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://beta-api.plune.ai/v1/bin/restore \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "kind": "suite", "id": "6f1c2b3a-0000-4000-8000-0000000000a1" }, { "kind": "case", "id": "6f1c2b3a-0000-4000-8000-0000000000c1" }, { "kind": "run", "id": "6f1c2b3a-0000-4000-8000-0000000000d1" } ], "dryRun": true }'The selection of POST /v1/bin/purge (one to 200 entries, the same items), for the same people and a manager of the project too (ADR 0050 §10), and the reverse of the three single restores — in ONE transaction, all or nothing: a suite entry comes back with its subtree and the cases that went with it, a case on its own, a run with the queue entries that went with it. The cases that come back — those named and those of the suites — are live cases again, so the project ceiling (testCasesPerProject) is asked ONCE for all of them: a pick that would cross it answers 429 Cannot restore: you reached the limit … and nothing changes. Two calls racing for the last free slot: one wins, the other is the 429. An item that is gone, already restored, or not in the Bin of the caller’s project makes the whole call 404 and restores nothing; the same 404 for a caller who may not restore. A case whose suite stays in the Bin (and is not in the pick) answers 409 suite_deleted, and a run whose key a live run holds answers 409 run_key_live, as the single restores do; nothing is restored then either. One restore audit entry per record named. dryRun: true does the same work, ceiling check included, and rolls it back, answering the counts the real call would have.
Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”The Bin entries a call acts on — to delete forever (POST /v1/bin/purge, #1020) or to bring back (POST /v1/bin/restore): one to 200 of the three kinds GET /v1/bin lists. All or nothing — when any of them is not in the caller’s project’s Bin, nothing is done and the whole call is answered 404.
object
object
suite names an ENTRY of the list — the node that was deleted, with its subtree and the cases that went with it; a node inside an entry is not one. case is a case deleted on its own, run a deleted run.
Do all of it and roll it back: the answer carries the counts the real call would have, and nothing is deleted.
Example
{ "items": [ { "kind": "suite", "id": "6f1c2b3a-0000-4000-8000-0000000000a1" }, { "kind": "case", "id": "6f1c2b3a-0000-4000-8000-0000000000c1" }, { "kind": "run", "id": "6f1c2b3a-0000-4000-8000-0000000000d1" } ], "dryRun": true}Responses
Section titled “ Responses ”What came back — or, on a dry run, what would
What POST /v1/bin/restore brought back — or, on a dryRun, would have.
object
Echoes the request. false means these rows are live again.
object
Suite nodes back: the whole subtree of each entry named.
Cases back — those named and those that went with a suite. This is the number the testCasesPerProject ceiling was asked about.
Example
{ "dryRun": true, "restored": { "suites": 3, "testCases": 12, "runs": 1 }}Invalid body — no item, more than 200, an unknown kind, an id that is not text, a dryRun that is not a boolean
object
Examples
{ "error": "validation failed — items: Too small: expected array to have >=1 items"}No / invalid token or session
The caller may not restore in this project, or an item is not (any more) an entry of its Bin; nothing was restored
object
Example
{ "error": "nothing restored: an item is not in the Bin of this project, or it was restored or deleted for good meanwhile"}suite_deleted: a case of the pick belongs to a suite that stays in the Bin. run_key_live: a run of the pick has the same key as a live run. Nothing was restored.
object
Example generated
{ "error": "example"}Two refusals share this status, and what to do about them is opposite. Rate-limited — the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442, #2092): Retry-After carries the seconds until the window rolls over, and waiting that long is the way out. Quota reached — a ceiling on what the project holds (runs in the last 24 hours, test cases, items waiting in the review queue): { error, quota }. A retry straight away frees nothing, so do not loop on it; the message says what makes room — deleting cases, working the queue down, an operator raising the limit — and the run quota, a rolling 24 hours, frees its oldest runs as they age out. Tell the two apart by the body: a 429 whose body has a quota object is a quota, one without it is a throttle. quota carries kind, limit, used, remaining and resetsAt: the figures behind the sentence, and for the run quota the instant its oldest run ages out (#1258). Retry-After is sent on a quota only where there is such an instant (the run quota: the whole seconds, at least 1, until resetsAt); the case, review-queue and project quotas free only by an action, have resetsAt: null, and send no Retry-After. GET /v1/quotas reads the same figures without a refusal.
object
The body of a 429 that is a quota and not a throttle: the sentence, and the figures behind it.
object
What was reached and the way out.
object
The ceiling. A write that would take used past it is refused with 429.
What the project holds now, counted by the same query that refuses a write.
limit - used, never below 0.
When the oldest counted run ages out of the rolling 24 hours and a slot frees on its own. null for the two quotas only an action frees (delete cases, decide queue items), and for a run quota with no runs in the window.
Example
{ "quota": { "kind": "runsPerDay" }}Headers
Section titled “Headers ”Seconds until the window rolls over on a throttle; on a quota, until quota.resetsAt — sent only for the run quota, which has one