Restore a version
restoreVersion
POST /api/v1/shares/{share_id}/versions/restore
files.overwriteRate cost: 5Make an earlier version of a file its current contents again (one of listVersions’ ids)
Send If-Match with the file’s ETag as you last read it (428 without, 412 stale). Foldr’s own hosted storage copies the version over the file within the storage; other storage that keeps versions (S3, Azure, SharePoint, an SMB server’s shadow copies) is given that version’s bytes, proven whole, so a restore that fails leaves the file as it was. Needs files.overwrite, allowed for your app on this share; refused as replacing a file is (checked out, legal hold, out for signature, published to this reader). Answers the file with its new ETag. With as_copy true the version is instead written beside the file as a new file (“Report (3 Nov 2026 14.02).docx”, the first free name) and nothing is replaced: no If-Match is needed, it is answered 201 with the new file, and it is not counted against the allowance or held for approval. On an SMB share, restoring over a file that no shadow copy holds (it changed since the newest one) is refused 409 not-recoverable-here, because the overwrite would lose it: restore as a copy instead. Send an Idempotency-Key: the same request sent again with it answers the first answer. For an app whose administrator requires approval for its changes, it is not made at once: once every check has passed (and the items are counted against the allowance) it is answered 202 with an operation in state pending_approval, held for someone other than the person to approve in Foldr, then made as the person; poll it at its Location (getOperation). Refused 409 no-approver when nobody else could approve it.
Request
Section titled “Request”curl -X POST "$FOLDR/api/v1/shares/$SHARE_ID/versions/restore" \ -H "Authorization: Bearer $TOKEN" \ -H "If-Match: $ETAG" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"path":"string","version_id":"string"}'$FOLDR is your site’s address (for example https://files.example.com) and $TOKEN is an access token. See Getting access.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
share_id | path | string | Yes | The share’s id. |
If-Match | header | string | Yes | The file’s ETag as you last read it. ”*” is refused. |
Idempotency-Key | header | string | Yes | A value of your own, 1 to 255 visible ASCII characters, new for each new request. The same key with the same request within 24 hours answers the first answer again and restores nothing more; with a different request it is refused (422). |
Request body
Section titled “Request body”application/json, required: RestoreVersionBody
| Field | Type | Description |
|---|---|---|
path | string | Required. The path of the file. |
version_id | string | Required. The version to restore: an id listVersions gave for this file. |
as_copy | boolean | true to write the version BESIDE the file as a new file named “name (3 Nov 2026 14.02).ext” (the first free name) and change nothing else. Needs no If-Match, is never refused for the file’s state (checked out, legal hold) and is not held for approval, because it replaces nothing. Default false: the version replaces the file. |
Example:
{ "path": "string", "version_id": "string"}Responses
Section titled “Responses”| Status | Meaning | Body |
|---|---|---|
| 200 | The file, with its new ETag. | application/json: Item |
| 201 | With as_copy: the new file, beside the original, with its ETag. | application/json: Item |
| 202 | Held for an approver (the app’s administrator requires approval for its changes): the operation, state pending_approval. Poll its Location after Retry-After seconds. | application/json: Operation |
200 response
Section titled “200 response”| Header | Description |
|---|---|
ETag | The entity tag of what was answered. Send it back as If-None-Match for a 304 while it is unchanged, or as If-Match to change it only while it is unchanged. |
Item:
| Field | Type | Description |
|---|---|---|
share_id | string (uuid) | Required. The id of the share the item is in. |
path | string | Required. The canonical path: as the storage holds it (its case), Unicode NFC, always starting with ”/”. ”/” is the top of the share. Send this form back in later calls. |
name | string | Required. The item’s name (the last segment of its path; the share’s name for the top). |
kind | string, one of file, folder | Required. file or folder. |
size | integer or null | Required. Size in bytes; null for a folder, and null when the item has no fixed byte size (a native Google document, exported on download: see export). |
modified | string or null (date-time) | Required. Last modified (RFC 3339, UTC), where the storage reports it. |
mime_type | string or null | Required. The item’s own media type, as the storage reports it or guessed from the name; null for a folder. A native Google document has Google’s own type here (application/vnd.google-apps.document for a Doc); export says what it downloads as. |
export | Export or null | Required. For a native Google document that can be downloaded, what content delivers: its media type and the extension the download is named with. Null for everything else, including a native Google type that cannot be downloaded (a Form: content answers 409 not-downloadable). |
etag | string or null | Required. The item’s entity tag: the storage’s own where it has one, else a weak tag over size and modified time (over modified time alone for a native Google document, whose export is made afresh each time). Null on a search hit, which comes from the index; GET …/item for the live one. |
read_only | boolean | Required. True when the person cannot change things in this share. |
checked_out | Checkout or null | Required. Who has the item checked out, or null. Null on a search hit. |
Example:
{ "share_id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "path": "string", "name": "string", "kind": "file", "size": 1, "modified": "2026-11-04T09:30:00Z", "mime_type": "string", "export": { "mime_type": "string", "extension": "string" }, "etag": "string", "read_only": false, "checked_out": { "by": {}, "since": "2026-11-04T09:30:00Z" }}201 response
Section titled “201 response”| Header | Description |
|---|---|
ETag | The entity tag of what was answered. Send it back as If-None-Match for a 304 while it is unchanged, or as If-Match to change it only while it is unchanged. |
Location | Where to read what was made. |
Item:
| Field | Type | Description |
|---|---|---|
share_id | string (uuid) | Required. The id of the share the item is in. |
path | string | Required. The canonical path: as the storage holds it (its case), Unicode NFC, always starting with ”/”. ”/” is the top of the share. Send this form back in later calls. |
name | string | Required. The item’s name (the last segment of its path; the share’s name for the top). |
kind | string, one of file, folder | Required. file or folder. |
size | integer or null | Required. Size in bytes; null for a folder, and null when the item has no fixed byte size (a native Google document, exported on download: see export). |
modified | string or null (date-time) | Required. Last modified (RFC 3339, UTC), where the storage reports it. |
mime_type | string or null | Required. The item’s own media type, as the storage reports it or guessed from the name; null for a folder. A native Google document has Google’s own type here (application/vnd.google-apps.document for a Doc); export says what it downloads as. |
export | Export or null | Required. For a native Google document that can be downloaded, what content delivers: its media type and the extension the download is named with. Null for everything else, including a native Google type that cannot be downloaded (a Form: content answers 409 not-downloadable). |
etag | string or null | Required. The item’s entity tag: the storage’s own where it has one, else a weak tag over size and modified time (over modified time alone for a native Google document, whose export is made afresh each time). Null on a search hit, which comes from the index; GET …/item for the live one. |
read_only | boolean | Required. True when the person cannot change things in this share. |
checked_out | Checkout or null | Required. Who has the item checked out, or null. Null on a search hit. |
Example:
{ "share_id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "path": "string", "name": "string", "kind": "file", "size": 1, "modified": "2026-11-04T09:30:00Z", "mime_type": "string", "export": { "mime_type": "string", "extension": "string" }, "etag": "string", "read_only": false, "checked_out": { "by": {}, "since": "2026-11-04T09:30:00Z" }}202 response
Section titled “202 response”| Header | Description |
|---|---|
Location | Where to poll the operation: GET it until it answers anything but 202. |
Retry-After | Seconds to wait before polling again. |
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Required. The operation’s id. |
kind | string | Required. What it does: copy, move (to another share, or held for approval within one), delete or restore (a version restore). delete and restore are only ever operations held for approval. Known values: copy, move, delete, restore. Clients must accept values not listed here. |
state | string, one of pending_approval, queued, running, cancelling, succeeded, failed, cancelled, declined, expired | Required. Where it has got to. pending_approval: held for an approver, nothing changed yet (approval says until when); queued: waiting to start; running: under way; cancelling: asked to stop, stopping at its next item; succeeded: done, result is what was made; failed: ended with the problem getOperation answers; cancelled: stopped, and what it had made removed (or, held for approval, withdrawn, nothing changed); declined: an approver said no, nothing changed; expired: nobody approved it in time, nothing changed. |
phase | string or null | Required. A move’s phase: copying (every item is copied first; nothing has been removed from where it was, and a cancel removes the partial copy), then deleting (the copy is whole and proven, and the originals are being removed: too late to cancel). Null for a copy. Known values: copying, deleting. Clients must accept values not listed here. |
created_at | string (date-time) | Required. When it was asked for (RFC 3339, UTC). |
started_at | string or null (date-time) | Required. When a worker first started it; null while queued. |
finished_at | string or null (date-time) | Required. When it finished; null until then. |
source | OperationSource | Required. What is being copied, moved, deleted or restored. |
target | OperationTarget or null | Required. Where it was asked to go: the path asked for. result.path is where it landed (on_conflict “rename” may have given it another name). Null for a delete or a restore. |
version_id | string or null | Required. For a restore: the version asked for (one of listVersions’ ids). Null otherwise. |
progress | OperationProgress | Required. How far it has got. |
result | Item or null | Required. Once it has succeeded, what it made (the copy, the moved item at its new place, the restored file), at its canonical path; null until then, and for a delete (see deletion). A move that ended source-remains also has it: the whole copy it made. |
deletion | Deletion or null | Required. A delete held for approval, once it has succeeded: what was deleted and where its storage keeps it now, as deleteItem answers. Null otherwise. |
approval | OperationApproval or null | Required. For an operation held for an approver: how its approval stands. Null for one that never waited for approval. |
failures | array of OperationFailure | Required. At most 100 items (failures_truncated says when there were more): for a copy that ended copy-incomplete, those it could not copy; for a move that ended source-remains, those still at the source. Empty otherwise. |
failures_truncated | boolean | Required. True when more items failed than failures lists. |
Example:
{ "id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "kind": "string", "state": "pending_approval", "phase": "string", "created_at": "2026-11-04T09:30:00Z", "started_at": "2026-11-04T09:30:00Z", "finished_at": "2026-11-04T09:30:00Z", "source": { "share_id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "path": "string", "kind": "file" }, "target": { "share_id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "path": "string" }, "version_id": "string", "progress": { "items_done": 1, "items_total": 1, "bytes_done": 1, "bytes_total": 1 }, "result": { "share_id": "0191f2c4-8a1e-7b52-9d3a-5c1e0b7a4d21", "path": "string", "name": "string", "kind": "file", "size": 1, "modified": "2026-11-04T09:30:00Z", "mime_type": "string", "export": { "mime_type": "string", "extension": "string" }, "etag": "string", "read_only": false, "checked_out": { "by": {}, "since": "2026-11-04T09:30:00Z" } }, "deletion": { "deleted": {}, "items": 1, "recoverable": "bin", "destination": "string", "where": "string" }, "approval": { "status": "string", "expires_at": "2026-11-04T09:30:00Z", "decided_at": "2026-11-04T09:30:00Z" }, "failures": [ { "path": "string", "code": "string" } ], "failures_truncated": false}Errors
Section titled “Errors”Every error is an application/problem+json document (ProblemDetails); branch on its type or code. See Errors.
| Status | Problem types | When |
|---|---|---|
| 400 | invalid-pathinvalid-request | One of: Invalid path (invalid-path); Invalid request (invalid-request). |
| 401 | invalid-token | Invalid token (invalid-token). |
| 403 | change-declinedinsufficient-scopelegal-holdwrite-not-allowed | One of: Change declined (change-declined); Insufficient scope (insufficient-scope); Legal hold (legal-hold); Not allowed for this app (write-not-allowed). |
| 404 | not-found | Not found (not-found). |
| 409 | ambiguous-pathchange-expiredenvelope-sourceno-approvernot-a-filenot-recoverable-hereoperation-cancelledrequester-not-connectedstorage-account-not-connected | One of: Ambiguous path (ambiguous-path); Change not approved in time (change-expired); Out for signature (envelope-source); No approver (no-approver); Not a file (not-a-file); Not recoverable here (not-recoverable-here); Operation cancelled (operation-cancelled); Not connected when approved (requester-not-connected); Storage account not connected (storage-account-not-connected). |
| 412 | changed-since-requestedprecondition-failed | One of: Changed since requested (changed-since-requested); Precondition failed (precondition-failed). |
| 413 | too-large | Too large (too-large). |
| 422 | idempotency-key-reused | Idempotency key reused (idempotency-key-reused). |
| 423 | checked-out | Checked out (checked-out). |
| 428 | precondition-required | Precondition required (precondition-required). |
| 429 | destructive-budgetrate-limited | One of: Daily limit of removals reached (destructive-budget); Too many requests (rate-limited). |
| 500 | internal-error | Internal error (internal-error). |
| 503 | share-unavailableshare-unreachablewrite-failed | One of: Share unavailable (share-unavailable); Share unreachable (share-unreachable); Write failed (write-failed). |