Storage Operations File and folder operations, locking, previews, and transforms
Storage endpoints are available on both workspaces and shares. The API patterns are identical — replace workspace/{workspace_id} with share/{share_id} in any path below unless noted as workspace-only or share-only.
All endpoints require JWT authentication unless otherwise noted. Include the header Authorization: Bearer {jwt_token} with every request.
Conventions
- Root folder: Use the literal string
"root"as the path parameter (e.g.,/storage/root/list/) - Trash folder: Use
"trash"to list trashed items (e.g.,/storage/trash/list/) - Node IDs: OpaqueIds — 29-character alphanumeric strings displayed with hyphens (e.g.,
f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf). Use as-is in API calls. - Node ID prefixes:
f= file,d= folder,n= note - Node types in responses:
"file","folder","note","link"(lowercase strings) - Parent field: Nodes at the storage root have
"parent": "root"; nested nodes show the parent's OpaqueId - Delete vs purge:
DELETE .../storage/{node_id}/delete/moves to trash.DELETE .../storage/trash/delete/empties the entire trash.DELETE .../storage/{node_id}/purge/permanently deletes a single trashed item. - Workspace folder shares: Shares that reference a workspace folder have their
rootmapped to the designated folder. All operations are scoped to that subtree. - Compact responses: Every storage endpoint that returns nodes (list, details, search, metadata, trash, quickshares) accepts an optional
?output=<token>query parameter with three detail levels:terse,standard, orfull. See the "Compact Responses" section below for the full contract, field lists, and the HTTP 400 rule for multi-level combinations. (QuickShare creation is deprecated — use the durable File Share instead.)
Compact Responses (output=)
Every storage endpoint that returns node objects — folder listings, node details, search hits, metadata endpoints, trash listings, and quickshares — accepts an optional output query parameter that selects the shape of each node in the response. A single detail-level token may be combined with modifier tokens; specifying two detail levels (e.g. ?output=terse,standard) returns HTTP 400. When output= is omitted, responses are full and byte-for-byte unchanged.
| Level | Fields returned on each node (cumulative) |
|---|---|
terse | id, type, name, parent, mimetype, size, modified, recursive nodes for folders when the endpoint returns children, summary reduced to {title} only, previews reduced to a per-type {ready: bool} map (keys preserved: thumbnail, image, pdf, mp4, hlsstream, audio, spreadsheet), folder-only is_share_root/share_id |
standard | terse + version, created, restricted, dmca, locked, mimecategory, origin reduced to {creator, type}, ai reduced to {state}, metadata reduced to {title, short} (user-authored overrides), deleted, deleted_from, is_imported, link-only target_type/target_id |
full | standard + summary.short/summary.long, note-only summary.category, virus, full ai object, full file_attributes (including EXIF), all remaining origin.* fields, hash, hash_algo, lock_info, import_metadata, full previews state map |
Use terse for list rows, tree rendering, pickers, breadcrumb navigation, and drag-and-drop targets — it carries modified (so list rows can render the date column and "sort by modified" without a follow-up fetch), a per-type previews readiness map (so the thumbnail selector can pick the best available source), and the summary title. Use standard for most detail views, file-browser main lists, and any UI that shows AI-processing state, lock/restricted chips, DMCA chips, import-provider chips, or trash state — it adds the ai.state that drives the "summarizing…" spinner, timestamps, origin creator plus type, the dmca flag for DMCA chip rendering, is_imported for import-provider chips, link-node target_type/target_id discriminators, and the metadata.title/metadata.short user overrides that list rows render when a custom title is set. Use full (or omit the parameter) for the node detail pane, virus/AI inspection, version history, and any workflow that reads long-form summaries, EXIF, import provider metadata, or content hashes. Unknown tokens are silently ignored. Add the markdown modifier (e.g. ?output=standard,markdown) to receive the response as GitHub-flavored Markdown (Content-Type: text/markdown; charset=UTF-8) instead of JSON — see the cross-cutting ?output= reference for the full contract.
Node Object Schema
All endpoints that return node data use this format. Fields vary by node type.
| Field | Type | Present On | Description |
|---|---|---|---|
id | string | all | OpaqueId of the node |
name | string | all | File, folder, or note name |
type | string | all | "file", "folder", "note", or "link" |
parent | string | all | Parent folder OpaqueId or "root" |
size | integer | file | File size in bytes |
hash | string | file | Content hash of the file |
hash_algo | string | file | Hash algorithm (e.g., "md5") |
mimetype | string | file | MIME type (e.g., "application/pdf") |
mimecategory | string | file | MIME category (e.g., "document", "image") |
version | string | file, note | Current version identifier (e.g., "v1") |
created | string | all | Creation timestamp (YYYY-MM-DD HH:MM:SS UTC) |
modified | string | all | Last-modified timestamp (YYYY-MM-DD HH:MM:SS UTC) |
restricted | boolean | all | Whether the file has been restricted |
dmca | boolean | all | Whether the file has a DMCA flag |
locked | boolean | all | Whether the node has an active lock |
lock_info | object/null | all | Lock details when locked; null otherwise |
virus | object | file | Virus scan status: {"status": "scanned", "infected": false} |
file_attributes | object | file | Media metadata: width, height, duration (where applicable) |
summary | object | file | AI-generated summary: {"title": "...", "short": "...", "long": "..."} |
metadata | object/null | file | User-defined custom title and description overrides |
previews | object | file | Preview generation state per type (e.g., {"thumbnail": {"state": "ready"}}) |
ai | object | file | AI processing state: {"state": "...", "attach": true/false} |
origin | object | file | Origin info: {"type": "upload", "creator": "{user_id}"} |
AI States
| Value | Description |
|---|---|
disabled | AI processing is disabled for this file |
pending | Queued for AI processing |
inprogress | AI processing is running |
ready | AI processing complete |
failed | AI processing failed |
indexed | File has been indexed for search and RAG |
Keyset Pagination (Storage List)
Storage listing endpoints (list and recent) use cursor-based pagination, not offset-based.
Request Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| sort_by | string | name | One of: name, updated, created, type |
| sort_dir | string | asc | One of: asc, desc |
| page_size | int | 100 | One of: 100, 250, 500 (snapped to nearest) |
| cursor | string | — | Opaque cursor string from previous response |
Response Pagination Fields
| Field | Type | Description |
|---|---|---|
pagination.has_more | boolean | Whether more pages exist |
pagination.next_cursor | string/null | Cursor for the next page; null if last page |
pagination.page_size | integer | Effective page size used |
Notes:
- Cursors are HMAC-signed; tampered cursors are rejected with an error.
- When using a cursor, the page size from the cursor takes precedence over the request parameter.
- Results for the first page may be slightly delayed.
- The
recentendpoint ignoressort_byandsort_dir(always sorted byupdateddescending).
List Folder Contents
/current/workspace/{workspace_id}/storage/{parent_id}/list/
/current/share/{share_id}/storage/{parent_id}/list/
List the contents of a folder. Uses keyset pagination.
Auth required. Permission: View (workspace), Guest+ (share). Share list on public shares may not require JWT.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {parent_id} | string | Yes | Folder OpaqueId, "root", or "trash" |
Query parameters: See Keyset Pagination section above.
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/root/list/?sort_by=name&sort_dir=asc&page_size=100" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"nodes": {
"items": [
{
"id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"type": "folder",
"name": "Documents",
"parent": "root",
"created": "2025-01-01 00:00:00 UTC",
"modified": "2025-01-20 14:45:00 UTC"
},
{
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "photo.jpg",
"parent": "root",
"size": 2048000,
"mimetype": "image/jpeg",
"created": "2025-01-10 09:00:00 UTC",
"modified": "2025-01-10 09:00:00 UTC"
}
],
"path": "/",
"parent": null
},
"pagination": {
"has_more": true,
"next_cursor": "eyJwIjoiMmFiYzEyMy4uLiIsInMi...",
"page_size": 100
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
nodes.items | array | Array of node objects for current page |
nodes.path | string | Current folder path |
nodes.parent | string/null | Parent folder ID or null for root |
pagination.has_more | boolean | true if more pages exist |
pagination.next_cursor | string/null | Cursor for next page |
pagination.page_size | integer | Actual page size used |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Folder not found |
1605 (Invalid Input) | 400 | Node is not a folder |
1605 (Invalid Input) | 400 | Invalid pagination cursor (tampered or mismatched) |
Node Details
/current/workspace/{workspace_id}/storage/{node_id}/details/
/current/share/{share_id}/storage/{node_id}/details/
Get full details for a single node (file, folder, or note).
Auth required. Permission: View (workspace), View (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/details/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"format": "single",
"node": {
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "document.pdf",
"parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"size": 5242880,
"hash": "d41d8cd98f00b204e9800998ecf8427e",
"hash_algo": "md5",
"mimetype": "application/pdf",
"mimecategory": "document",
"version": "v1",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-20 14:45:00 UTC",
"restricted": false,
"dmca": false,
"locked": false,
"lock_info": null,
"virus": { "status": "scanned", "infected": false },
"file_attributes": { "width": null, "height": null, "duration": null },
"summary": {
"title": "Quarterly Report",
"short": "Q4 financial summary",
"long": "Comprehensive financial report covering revenue and expenses."
},
"metadata": null,
"previews": {
"thumbnail": { "state": "ready" },
"pdf": { "state": "ready" }
},
"ai": { "state": "indexed", "attach": true },
"origin": { "type": "upload", "creator": "9876543210987654321" }
}
}
Response fields: See Node Object Schema above for complete field reference.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1680 (Access Denied) | 403 | No permission to view details (share only) |
Bulk Form
The details endpoint accepts a comma-separated list of node ids in place of a single {node_id}:
GET /current/workspace/{workspace_id}/storage/{id1},{id2},{id3}/details/
Up to 25 ids per call. Duplicate ids are silently deduplicated. Empty segments (e.g. trailing comma) return 400. The bulk form is currently only available for the workspace path.
The bulk response shape differs from the single-id form. Successfully resolved nodes appear as an array under nodes; per-id failures appear in a parallel errors array. HTTP status is 200 when at least one node resolves and 404 when every requested id errored.
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/abc123,def456,ghi789/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (HTTP 200)
{
"result": true,
"format": "multi",
"nodes": [
{ "id": "abc123", "type": "file", "name": "report.pdf" },
{ "id": "def456", "type": "folder", "name": "Documents" }
],
"errors": [
{ "node_id": "ghi789", "code": 133123, "message": "No such file or folder exists" }
]
}
Every details response carries a top-level format field — "single" (single-id form, paired with a node object) or "multi" (bulk form, paired with nodes + errors arrays). Clients can branch on format instead of inferring shape from key presence.
Bulk Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | Empty segment between commas |
1605 (Invalid Input) | 400 | More than 25 unique ids in one request |
1609 (Not Found) | 404 | Every requested id errored (errors array is populated) |
Add File from Upload
/current/workspace/{workspace_id}/storage/{parent_id}/addfile/
/current/share/{share_id}/storage/{parent_id}/addfile/
Add a previously uploaded file to storage.
Auth required. Permission: Guest (workspace), file creation permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {parent_id} | string | Yes | Parent folder OpaqueId or "root" |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Filename for the new file |
| from | string | Yes | JSON-encoded source object (see below) |
from format:
Both workspace and share variants accept the same JSON shape. Two source types are supported: upload (a completed upload session) and hash (deduplicate against an existing object by content hash).
{"type": "upload", "upload": {"id": "{upload_id}"}}
{"type": "hash", "hash": {"hash": "{file_hash}", "hash_type": "sha256"}}
A hash source adds the file instantly only when its content already exists in that storage; if the content is not present, the call returns 404 Not Found ("The specified file content was not found in this storage. Upload the file instead.") and the client should upload the file normally instead.
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/root/addfile/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'name=document.pdf' \
-d 'from={"type":"upload","upload":{"id":"abc123opaqueid"}}'
Response
{
"result": true,
"node": {
"id": "f4kn6-arybw-qxes3-ey9z6-cwoc4-sglh",
"type": "file",
"name": "document.pdf",
"parent": "root",
"size": 5242880,
"hash": "d41d8cd98f00b204e9800998ecf8427e",
"hash_algo": "md5",
"mimetype": "application/pdf",
"mimecategory": "document",
"version": "v1",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-15 10:30:00 UTC",
"restricted": false,
"dmca": false,
"locked": false
}
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | Upload session not found or not associated with your account |
1605 (Invalid Input) | 400 | Upload is not complete |
1609 (Not Found) | 404 | Parent folder not found |
1605 (Invalid Input) | 400 | Parent node is not a folder |
1609 (Not Found) | 404 | Parent folder is in trash |
1605 (Invalid Input) | 400 | Name conflict (only when using FAIL strategy) |
1693 (Temporarily Unavailable) | 503 | Chunk manifest not yet durable — retry after a brief delay |
Notes:
- The upload session must be in COMPLETE status before adding the file.
- Virus scanning occurs during upload assembly, not at this stage.
- Conflict resolution: If a file with the same name exists, the default behavior is to replace (overwrite) the existing file, creating a version for rollback. Folder or type-mismatch conflicts fall back to renaming.
Add Link (Workspace Only)
/current/workspace/{workspace_id}/storage/{parent_id}/addlink/
Add a share link node to workspace storage. Link nodes represent references to shares within the workspace tree.
Auth required. Permission: Guest.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit workspace profile ID |
| {parent_id} | string | Yes | Parent folder OpaqueId or "root" |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| link_target_type | string | Yes | Must be "share" |
| share | string | Yes | Share identifier to link |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/root/addlink/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'link_target_type=share' \
-d 'share=my-share-name'
Response
{"result": true}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1683 (Resource Missing) | 404 | Share not found or not accessible |
1605 (Invalid Input) | 400 | Share does not belong to this workspace |
1605 (Invalid Input) | 400 | A link to this share already exists (only one per share) |
Create Folder
/current/workspace/{workspace_id}/storage/{parent_id}/createfolder/
/current/share/{share_id}/storage/{parent_id}/createfolder/
Create a new folder.
This endpoint is idempotent by default: if a folder with the same name already exists in the parent, the existing folder is returned (HTTP 200) instead of creating a duplicate. Pass force=true to bypass this and always create a new folder (auto-renamed on a name collision, e.g. Documents (2)).
Auth required. Permission: Guest (workspace), folder creation permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {parent_id} | string | Yes | Parent folder OpaqueId or "root" |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Folder name |
| force | boolean | No | When true, always create a new folder even if one with this name already exists (auto-renamed on collision). Defaults to false (idempotent — returns the existing folder). |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/root/createfolder/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'name=Documents'
Response
{
"result": true,
"node": {
"id": "d2bcd-efghi-jklmn-opqrs-tuvwx-yz12",
"type": "folder",
"name": "Documents",
"parent": "root",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-15 10:30:00 UTC",
"restricted": false,
"dmca": false,
"locked": false
}
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | A non-folder item already uses this name in the parent folder |
1609 (Not Found) | 404 | Parent folder not found |
1605 (Invalid Input) | 400 | Parent node is not a folder |
1609 (Not Found) | 404 | Parent folder is in trash |
1680 (Access Denied) | 403 | No folder creation permission (share only) |
Create Note (Workspace Only)
/current/workspace/{workspace_id}/storage/{parent_id}/createnote/
Create a markdown note. Notes are auto-indexed for AI when workspace intelligence is enabled.
Auth required. Permission: Guest.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit workspace profile ID |
| {parent_id} | string | Yes | Parent folder OpaqueId or "root" |
Request Body (form-encoded)
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| name | string | Yes | Must end in .md | Note name |
| content | string | Yes | Max 100 KB | Markdown content |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/root/createnote/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'name=meeting-notes.md' \
-d 'content=# Meeting Notes\n\nDiscussed project timeline.'
Response
{
"result": true,
"note": {
"id": "n5mno-pqrst-uvwxy-z1234-abcde-fghi",
"type": "note",
"name": "meeting-notes.md",
"parent": "root",
"mimetype": "text/markdown",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-15 10:30:00 UTC"
}
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | Filename must end with .md |
1605 (Invalid Input) | 400 | Duplicate name in parent folder |
1609 (Not Found) | 404 | Parent folder not found |
Update Note (Workspace Only)
/current/workspace/{workspace_id}/storage/{node_id}/updatenote/
Update an existing note's name and/or content. Updating content creates a new version.
Auth required. Permission: Guest.
This workspace endpoint also accepts a realtime-note bearer token (minted by GET /current/realtime/note-auth/{profile_id}/{note_id}) in place of a user JWT, for the collaborative-editing backend to save on the user's behalf. The token is bound to a specific note and workspace and is rejected (403 Forbidden) if it does not match the requested note or workspace, or if it carries only view permission (a view token cannot update). The share note endpoints do not accept this token.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit workspace profile ID |
| {node_id} | string | Yes | Note OpaqueId |
Request Body (form-encoded)
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| name | string | No | Must end in .md | New note name |
| content | string | No | Max 100 KB, non-blank | New markdown content (an empty or whitespace-only value is rejected) |
| if_version_id | string | No | Version OpaqueId | Compare-and-swap precondition. When supplied, the update only proceeds if the note's current version matches this value; otherwise the request is rejected with 409 Conflict and no change is made. Use the version returned by a prior read or update to guard against overwriting concurrent edits. |
At least one of name or content is required.
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghi/updatenote/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'content=# Updated Notes\n\nRevised content here.'
Response
{
"result": true,
"note": {
"id": "n5mno-pqrst-uvwxy-z1234-abcde-fghi",
"type": "note",
"name": "meeting-notes.md",
"parent": "root",
"mimetype": "text/markdown",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-20 14:45:00 UTC"
}
}
Conflict Response (if_version_id mismatch)
When if_version_id is supplied and does not match the note's current version, the request is rejected with HTTP 409 Conflict, no version is created, and the current state is returned under error.params.current so the client can rebase its edit:
{
"error": {
"code": 166226,
"text": "The note was modified since the supplied version",
"params": {
"current": {
"version_id": "v9abc-defgh-ijklm-nopqr-stuvw-xyz0",
"hash": "d41d8cd98f00b204e9800998ecf8427e"
}
}
}
}
If the current state cannot be resolved at the time of the conflict, the affected field(s) under current may be omitted.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Note not found |
1605 (Invalid Input) | 400 | Node is not a note |
1609 (Not Found) | 404 | Note is in trash |
1605 (Invalid Input) | 400 | No content or name provided |
1605 (Invalid Input) | 400 | Duplicate name in parent folder |
1660 (Conflict) | 409 | if_version_id did not match the note's current version |
1700 (Forbidden) | 403 | realtime-note token does not match the requested note or workspace |
1700 (Forbidden) | 403 | realtime-note token lacks edit permission (a view token cannot update) |
Read Note
/current/workspace/{workspace_id}/storage/{node_id}/readnote/
/current/share/{share_id}/storage/{node_id}/readnote/
Read a note's content as JSON. Unlike the binary /read/ endpoint, this returns the sanitized markdown content as a string within the JSON response along with the full note resource.
Auth required. Permission: View (workspace), download permission or download token (share).
The workspace endpoint also accepts a realtime-note bearer token (minted by GET /current/realtime/note-auth/{profile_id}/{note_id}) in place of a user JWT, for the collaborative-editing backend to read on the user's behalf. The token is bound to a specific note and workspace and is rejected (403 Forbidden) if it does not match the requested note or workspace. The share note endpoint does not accept this token.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Note OpaqueId |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| version_id | string | No | Specific version OpaqueId to read |
| token | string | No | Download token (share only — bypasses JWT auth) |
curl Example
# Workspace
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghi/readnote/" \
-H "Authorization: Bearer {jwt_token}"
# Share
curl -X GET "https://api.fast.io/current/share/1234567890123456789/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghi/readnote/" \
-H "Authorization: Bearer {jwt_token}"
# Share with download token (no JWT needed)
curl -X GET "https://api.fast.io/current/share/1234567890123456789/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghi/readnote/?token={download_token}"
Response
{
"result": true,
"content": "# Meeting Notes\n\nDiscussed project timeline.",
"note": {
"id": "n5mno-pqrst-uvwxy-z1234-abcde-fghi",
"type": "note",
"name": "meeting-notes.md",
"parent": "root",
"mimetype": "text/markdown",
"version": "v1",
"created": "2025-01-15 10:30:00 UTC",
"modified": "2025-01-15 10:30:00 UTC"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
content | string | Sanitized markdown content of the note |
note | object | Full note node object (same shape as other node responses) |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | Invalid node ID |
1609 (Not Found) | 404 | Note not found |
1605 (Invalid Input) | 400 | Node is not a note |
1609 (Not Found) | 404 | Note is in trash |
1609 (Not Found) | 404 | Version not found |
1605 (Invalid Input) | 400 | Version does not belong to this note |
1609 (Not Found) | 404 | Version data no longer available |
1680 (Access Denied) | 403 | No permission to read notes (share only) |
1680 (Access Denied) | 403 | No permission to read notes you did not create (share, creator-only restriction) |
1700 (Forbidden) | 403 | realtime-note token does not match the requested note or workspace |
1700 (Forbidden) | 403 | realtime-note token lacks read permission |
Update Node
/current/workspace/{workspace_id}/storage/{node_id}/update/
/current/share/{share_id}/storage/{node_id}/update/
Update a node: rename, replace content with a new upload, and/or update custom metadata. Cannot be used on Note nodes (use updatenote instead).
Auth required. Permission: Guest (workspace), file modification permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId |
Request Body (form-encoded)
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| name | string | No | — | New node name |
| from | string | No | JSON-encoded | New file content source (same format as addfile) |
| metadata_title | string | No | Max 50 chars | Custom title override |
| metadata_short | string | No | Max 2048 chars | Custom short description override |
At least one field should be provided.
curl Example (rename)
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/update/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'name=new-document-name.pdf'
curl Example (replace content and update metadata)
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/update/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'from={"type":"upload","upload":{"id":"upload_opaque_id"}}' \
-d 'metadata_title=Updated Report' \
-d 'metadata_short=Q1 2025 revision'
Response
{
"result": true,
"node": {
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "new-document-name.pdf",
"parent": "root",
"size": 5242880,
"version": "v2",
"modified": "2025-01-22 11:00:00 UTC"
}
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1605 (Invalid Input) | 400 | Cannot update a Note with this endpoint |
1605 (Invalid Input) | 400 | No update parameters were specified |
1605 (Invalid Input) | 400 | Cannot update a folder or link with file data |
1605 (Invalid Input) | 400 | Name conflict — a file or folder with that name already exists in this location |
1693 (Temporarily Unavailable) | 503 | Chunk manifest not yet durable when replacing content — retry after a brief delay |
1609 (Not Found) | 404 | Node is in trash |
1680 (Access Denied) | 401 | No modify permission (share only) |
1680 (Access Denied) | 401 | No permission to modify files you did not create (share, creator-only restriction) |
Notes:
- At least one of
name,from,metadata_title, ormetadata_shortmust be provided. - Replacing content creates a new version.
- Renaming a link node propagates the rename to the linked share.
- Custom metadata overrides AI-generated summary values for display.
- Share permissions may restrict modification to files the user created (creator-only restrictions).
Move Node
/current/workspace/{workspace_id}/storage/{node_id}/move/
/current/share/{share_id}/storage/{node_id}/move/
Move a node to a different folder within the same storage instance.
Auth required. Permission: Guest (workspace), file modification permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId to move |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| parent | string | Yes | Destination folder OpaqueId or "root" |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/move/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'parent=d1abc-defgh-ijklm-nopqr-stuvw-xyz1'
Response
{"result": true}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Source or destination node not found |
1609 (Not Found) | 404 | Source or destination is in trash |
1605 (Invalid Input) | 400 | Cannot move a folder into itself or its subfolders |
1680 (Access Denied) | 401 | No move permission (share only) |
1680 (Access Denied) | 401 | No permission to move a folder containing files you cannot modify (recursive move requires modify-all, share only) |
Notes:
- Guests who can view or modify only their own files may copy or move individual files they created; copying or moving a whole folder requires permission to view (copy) or modify (move) all files in the share.
- Conflict resolution: If a file with the same name exists in the destination, the existing file is replaced (moved to trash for rollback). Folder or type-mismatch conflicts fall back to renaming.
Copy Node
/current/workspace/{workspace_id}/storage/{node_id}/copy/
/current/share/{share_id}/storage/{node_id}/copy/
Copy a node to another folder within the same storage instance. Folder copies are recursive.
Auth required. Permission: Guest (workspace), file/folder creation permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId to copy |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| parent | string | Yes | Destination folder OpaqueId or "root" |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/copy/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'parent=d1abc-defgh-ijklm-nopqr-stuvw-xyz1'
Response
{
"result": true,
"node": {
"id": "f6qrs-tuvwx-yz123-abcde-fghij-klmn",
"type": "file",
"name": "document.pdf",
"parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1"
},
"job": null
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Source or destination not found |
1605 (Invalid Input) | 400 | Destination is not a folder |
1609 (Not Found) | 404 | Source or destination is in trash |
1680 (Access Denied) | 401 | No permission to view or copy files you did not create (share, creator-only restriction) |
1680 (Access Denied) | 401 | No permission to copy a folder containing files you cannot view (recursive copy requires view-all, share only) |
Notes:
- Creates a deep copy for folders (all children are copied recursively).
- Guests who can view only their own files may copy or move individual files they created; copying or moving a whole folder requires permission to view (copy) or modify (move) all files in the share.
- The copied node gets a new OpaqueId.
- Conflict resolution: If a file with the same name exists in the destination, the existing file is replaced (moved to trash for rollback). Folder or type-mismatch conflicts fall back to renaming.
Transfer Node
/current/workspace/{workspace_id}/storage/{node_id}/transfer/
/current/share/{share_id}/storage/{node_id}/transfer/
Copy or move a node to a different storage instance (e.g., from workspace to share, share to workspace, or share to share). By default the original node remains in place (copy). Use mode=move to copy the node and then trash the source.
Auth required. Permission: Guest on source + write access on destination.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | Source profile ID |
| {node_id} | string | Yes | Node OpaqueId to transfer, or "root" for all |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| instance | string | Yes | 19-digit destination workspace or share profile ID |
| parent | string | Yes | Destination parent folder OpaqueId or "root" |
| mode | string | No | copy (default) or move. When move, the source node is trashed after copying. Cannot use mode=move when {node_id} is "root". |
curl Example (copy, default)
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/transfer/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'instance=9876543210987654321' \
-d 'parent=root'
curl Example (move)
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/transfer/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'instance=9876543210987654321' \
-d 'parent=root' \
-d 'mode=move'
Response (copy)
{
"result": true,
"node": {
"id": "f7stu-vwxyz-12345-abcde-fghij-klmp",
"type": "file",
"name": "document.pdf",
"parent": "root"
},
"job": null
}
Response (move)
{
"result": true,
"node": {
"id": "f7stu-vwxyz-12345-abcde-fghij-klmp",
"type": "file",
"name": "document.pdf",
"parent": "root"
},
"source_trashed": true,
"job": null
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Source node not found or outside scope |
1609 (Not Found) | 404 | Source or destination instance (workspace/share) not found |
1680 (Access Denied) | 401 | No modify permission on the source node (share only) |
1680 (Access Denied) | 401 | No permission to modify source files you did not create (share, creator-only restriction) |
1680 (Access Denied) | 401 | No permission to transfer a whole folder or root (recursive copy requires view-all, move requires modify-all, share only) |
1680 (Access Denied) | 401 | No write access to destination -- the caller lacks file-creation permission on the destination share (e.g. a send-type share where guests cannot create) |
1680 (Access Denied) | 401 | Destination is a password-protected public share and a valid share password was not supplied |
1680 (Access Denied) | 401 | Writing into a personal (user-owned) share requires an identified caller with creation permission (an unauthenticated caller is denied) |
1680 (Access Denied) | 401 | The request token's scope does not cover the destination share (a scoped token must include the destination share, or its parent workspace/org) |
1605 (Invalid Input) | 400 | Name conflict at destination, or unsupported/not-allowed transfer |
1605 (Invalid Input) | 400 | mode=move cannot be used with "root" as the source node |
Notes:
- Folder transfers are recursive.
- The user must have write access to both source and destination. Writing into a destination share requires file-creation permission on that share, honoring its type and access settings; a password-protected public destination share also requires a valid share password.
- When
mode=move, the source node is trashed in the source storage instance after the copy completes. The response includes"source_trashed": trueon success. - Guests who can view or modify only their own files may copy or move individual files they created; copying or moving a whole folder requires permission to view (copy) or modify (move) all files in the share.
Delete Node (Move to Trash)
/current/workspace/{workspace_id}/storage/{node_id}/delete/
/current/share/{share_id}/storage/{node_id}/delete/
Move a node to trash. Pass "trash" as the {node_id} to empty the entire trash bin.
Auth required. Permission: Guest (workspace), file modification permission (share). Emptying trash on shares requires admin permission.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId, or "trash" to empty the entire trash |
curl Examples
# Delete a specific node
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/delete/" \
-H "Authorization: Bearer {jwt_token}"
# Empty the trash
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/storage/trash/delete/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"job": null
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1609 (Not Found) | 404 | Node already in trash |
1680 (Access Denied) | 403 | No delete permission (share only) |
1680 (Access Denied) | 403 | No permission to empty trash (share, non-admin) |
Notes:
- Deleting a folder moves it and all children to trash recursively.
- Share delete permissions may be restricted to files the user created.
Purge Node (Permanent Delete)
/current/workspace/{workspace_id}/storage/{node_id}/purge/
/current/share/{share_id}/storage/{node_id}/purge/
Permanently delete a node that is already in trash. Irreversible.
Auth required. Permission: Member (workspace), admin (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | OpaqueId of the trashed node |
curl Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/purge/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"job": null
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1605 (Invalid Input) | 400 | Node is not in trash |
1680 (Access Denied) | 403 | Insufficient permission (share, non-admin) |
Restore from Trash
/current/workspace/{workspace_id}/storage/{node_id}/restore/
/current/share/{share_id}/storage/{node_id}/restore/
Restore a trashed node to its original location.
Auth required. Permission: Guest (workspace), file modification + admin (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | OpaqueId of the trashed node |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/restore/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"job": null
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1605 (Invalid Input) | 400 | Node is not in trash |
1605 (Invalid Input) | 400 | Node is inside a trashed folder (restore the parent folder instead) |
1680 (Access Denied) | 403 | No restore permission (share only) |
List Versions
/current/workspace/{workspace_id}/storage/{node_id}/versions/
/current/share/{share_id}/storage/{node_id}/versions/
List all versions of a file or note.
Auth required. Permission: View (workspace), file view permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | Node OpaqueId |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/versions/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"versions": [
{
"id": "v8abc-defgh-ijklm-nopqr-stuvw-xyz9",
"version_number": 2,
"created": "2025-01-20 14:45:00 UTC",
"operation": "update",
"size": 5242880,
"hash": "abc123def456789..."
},
{
"id": "v9bcd-efghi-jklmn-opqrs-tuvwx-yz01",
"version_number": 1,
"created": "2025-01-15 10:30:00 UTC",
"operation": "create",
"size": 4194304,
"hash": "def456abc789012..."
}
]
}
Response Fields (per version)
| Field | Type | Description |
|---|---|---|
id | string | Version OpaqueId |
version_number | integer | Incrementing version number |
created | string | Version creation timestamp (YYYY-MM-DD HH:MM:SS UTC) |
operation | string | How this version was created: "create", "update", "restore" |
size | integer | File size in bytes for this version |
hash | string | Content hash for this version |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1605 (Invalid Input) | 400 | Unsupported node type |
1665 (Object Init Failed) | 500 | Node data is corrupted |
1680 (Access Denied) | 401 | No permission to view file versions (share only) |
1680 (Access Denied) | 401 | No permission to view versions of files you did not create (share, creator-only restriction) |
Notes:
- Share permissions may restrict version listing to files the user created (creator-only restrictions).
Restore Version
/current/workspace/{workspace_id}/storage/{node_id}/restore-version/
/current/share/{share_id}/storage/{node_id}/restore-version/
Restore a file to a previous version. Creates a new version pointing to the historical version's content. Both filename and content are restored.
Auth required. Permission: Guest (workspace), file modification permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | File OpaqueId |
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| version_id | string | Yes | OpaqueId of the version to restore |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/restore-version/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'version_id=v9bcd-efghi-jklmn-opqrs-tuvwx-yz01'
Response
{
"result": true,
"new_version": {
"id": "v0cde-fghij-klmno-pqrst-uvwxy-z123",
"version_number": 3,
"created": "2025-01-25 09:00:00 UTC",
"operation": "restore"
},
"node": {
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "original-name.pdf",
"version": "v3"
}
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found |
1605 (Invalid Input) | 400 | Can only restore file versions (not folders) |
1605 (Invalid Input) | 400 | Cannot restore version of trashed file |
1609 (Not Found) | 404 | Version not found |
1605 (Invalid Input) | 400 | Version does not belong to this file |
1609 (Not Found) | 404 | Version data no longer available |
1680 (Access Denied) | 403 | No permission to restore versions (share only) |
Notes:
- Original versions are preserved; a new version with operation type
"restore"is created. - Both filename and content are restored to the historical version's state.
Download File (Read)
/current/workspace/{workspace_id}/storage/{node_id}/read/
/current/share/{share_id}/storage/{node_id}/read/
Download file content as binary. For notes, returns raw markdown. Supports byte-range requests for partial downloads and video streaming.
Auth: JWT or download token. Permission: View (workspace), download permission (share). With a valid token query parameter, no JWT is required.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | File or note OpaqueId |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| token | string | No | Download token from requestread (bypasses JWT auth) |
| version_id | string | No | Specific version OpaqueId to download |
curl Examples
# Download with JWT auth
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/read/" \
-H "Authorization: Bearer {jwt_token}" \
-o output.pdf
# Download with token (no JWT needed)
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/read/?token={download_token}" \
-o output.pdf
# Download specific version
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/read/?version_id=v9bcd-efghi-jklmn-opqrs-tuvwx-yz01" \
-H "Authorization: Bearer {jwt_token}" \
-o output_v1.pdf
# Byte-range request (streaming)
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/read/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Range: bytes=0-1023"
Response: Binary file content streamed directly.
- Status
200 OKfor full file,206 Partial Contentfor range requests. - Headers:
Content-Type,Content-Length,Content-Disposition,Accept-Ranges: bytes.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | File not found |
1605 (Invalid Input) | 400 | Can only read file or note (not folder) |
1609 (Not Found) | 404 | File is in trash |
1609 (Not Found) | 404 | Version not found |
1605 (Invalid Input) | 400 | Version does not belong to this file |
1609 (Not Found) | 404 | Version data no longer available |
1680 (Access Denied) | 403 | File flagged as virus-infected (share only) |
Request Download Token
/current/workspace/{workspace_id}/storage/{node_id}/requestread/
/current/share/{share_id}/storage/{node_id}/requestread/
Generate a temporary auth-free download token.
Auth required. Permission: View (workspace), download permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {node_id} | string | Yes | File or note OpaqueId |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/requestread/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Usage: Append ?token={token} to the read endpoint to download without an Authorization header. Useful for opening files in browser tabs.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | File not found |
1605 (Invalid Input) | 400 | Can only read file or note |
1609 (Not Found) | 404 | File is in trash |
1680 (Access Denied) | 403 | No download permission (share only) |
Download Folder as ZIP
/current/workspace/{workspace_id}/storage/{folder_id}/zip/
/current/share/{share_id}/storage/{folder_id}/zip/
Download an entire folder as a streaming ZIP archive.
Auth required. Permission: View (workspace), download permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} or {share_id} | string | Yes | 19-digit profile ID |
| {folder_id} | string | Yes | Folder OpaqueId, or "root" for entire storage |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/root/zip/" \
-H "Authorization: Bearer {jwt_token}" \
-o workspace.zip
Response: Binary ZIP archive streamed directly with Content-Type and Content-Disposition headers.
Notes:
- Uses ZIP64 format — supports archives up to 50GB (plan-dependent limits may be lower).
- Compatible with all modern extraction tools (WinRAR, 7-Zip, macOS Archive Utility, Windows Explorer).
- The archive is streamed; it is not buffered in memory.
- Files are stored without compression for immediate streaming.
- Maximum 10,000 files per archive.
- Rate limited more aggressively than other endpoints due to resource cost.
Recent Files
/current/workspace/{workspace_id}/storage/recent/
/current/share/{share_id}/storage/recent/
List recently modified nodes across all folders, sorted by updated descending. Unlike list which is scoped to a single folder, this endpoint returns nodes from the entire storage tree.
Auth required. Permission: View (workspace), Guest+ (share). Public shares may allow password-only access.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page_size | int | 100 | One of: 100, 250, 500 (snapped to nearest) |
| cursor | string | — | Opaque cursor string from previous response |
| type | string | — | Filter by node type: file, folder, link, note |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/recent/?type=file&page_size=250" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"nodes": {
"count": 3,
"items": [
{
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"name": "report.pdf",
"type": "file",
"parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"size": 2048576,
"updated": "2025-02-18 14:30:00 UTC",
"created": "2025-02-17 10:00:00 UTC"
}
]
},
"pagination": {
"has_more": true,
"next_cursor": "eyJsYXN0X3VwZGF0ZWQiOiIyMDI1LTAyLTE4...",
"page_size": 100
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
nodes.count | integer | Number of nodes in this page |
nodes.items | array | Array of node resources |
pagination.has_more | boolean | Whether more pages exist |
pagination.next_cursor | string/null | Cursor for the next page |
pagination.page_size | integer | Effective page size used |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | Invalid pagination cursor |
1680 (Access Denied) | 403 | Insufficient permissions to view files (share only) |
1609 (Not Found) | 404 | Orphaned workspace folder share |
Notes:
- Sort order is always
updated DESCand is not configurable. - Uses cursor-based (keyset) pagination, same as the
listendpoint. - For workspace folder shares, results are post-filtered to the share's subtree.
Search
/current/workspace/{workspace_id}/storage/search/
/current/share/{share_id}/storage/search/
Search files by keyword. Uses keyword and semantic search.
Auth required. Permission: View (workspace), search + file view permissions (share).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| search | string | Yes | Search query string |
| output | string | No | Verbosity: terse, standard, or full (default). Trims content_snippet to a byte budget when intelligence is enabled. See Verbosity below. |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/search/?search=quarterly+report" \
-H "Authorization: Bearer {jwt_token}"
Response (keyword-only, intelligence disabled)
{
"result": true,
"files": {
"f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf": {
"name": "Q4 Report.pdf",
"parent_id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"type": "file"
}
}
}
Response (hybrid, intelligence enabled)
{
"result": true,
"files": {
"f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf": {
"name": "Q4 Report.pdf",
"parent_id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"type": "file",
"relevance_score": 0.93,
"content_snippet": "Quarterly revenue grew 18% year-over-year …",
"match_source": "both",
"media_segment": null,
"mimetype": "application/pdf",
"page": { "start_page": 3, "end_page": 3 }
}
},
"search_metadata": {
"intelligence_enabled": true,
"semantic_available": true,
"scoped": false
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
files | object | Map of node OpaqueIds to file info |
files.{id}.name | string | File name |
files.{id}.parent_id | string | Parent folder node ID |
files.{id}.type | string | Node type |
files.{id}.relevance_score | float | Relevance score (0.0–1.0). Hybrid mode only. |
files.{id}.content_snippet | string|null | Matching text from the semantic chunk; null for keyword-only matches. Trimmed per output. Hybrid mode only. |
files.{id}.match_source | string | keyword, semantic, or both. Hybrid mode only. |
files.{id}.media_segment | object|null | {start_seconds, end_seconds} for audio/video matches. Hybrid mode only. |
files.{id}.mimetype | string|null | File MIME type. Hybrid mode only. |
files.{id}.page | object|null | {start_page, end_page} for paginated documents. Hybrid mode only. |
search_metadata | object | {intelligence_enabled, semantic_available, scoped}. Hybrid mode only. |
Verbosity (output)
content_snippet is the only field affected by output=. All other fields are returned unchanged at every level.
output | content_snippet shape |
|---|---|
terse | First ~200 bytes of the matching chunk, UTF-8 safe. Truncated values end with …. |
standard | First ~600 bytes (roughly one paragraph). Same … suffix when truncated. |
full (default) | Full matching chunk, untrimmed. |
Snippets shorter than the budget are returned unchanged (no padding, no …). Null/empty snippets are returned unchanged at every level. The byte budget is inclusive of the trailing … so the wire payload never exceeds the cap. Default output=full preserves prior behavior for callers that omit the parameter.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Search not available for workspace folder shares (share only) |
1680 (Access Denied) | 403 | No search permission (share only) |
Notes:
- Share search is not available for workspace-backed shares (shared folders).
- Search results are filtered by the user's file view permissions.
Metadata Search (Workspace Only)
/current/workspace/{workspace_id}/metadata/search/
Keyword search across the metadata key-value pairs stored on workspace files (template fields plus custom fields). Returns matching nodes ordered by relevance. Trashed nodes are filtered out automatically. The endpoint is workspace-scoped — results never cross workspace boundaries regardless of caller input.
Auth required. Permission: View (workspace member).
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| q | string | Yes | — | Keyword query. Whitespace-trimmed; an empty value is rejected. |
| template_id | string | No | — | Restrict matches to nodes that have at least one metadata value contributed by this template. Custom (template-independent) fields are not included when this filter is set. |
| limit | int | No | 100 | Maximum number of results (1-100). |
| offset | int | No | 0 | Number of results to skip. Requests beyond the supported result window return an input error. |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/metadata/search/?q=invoice&limit=25" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"results": [
{
"node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"score": 4.215,
"template_ids": ["mt_abc123def456"],
"node": {
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "Invoice-2026-0042.pdf",
"parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"size": 524288,
"mimetype": "application/pdf",
"mimecategory": "document",
"version": "v1",
"created": "2026-04-20 14:12:08 UTC",
"modified": "2026-04-22 09:30:55 UTC",
"restricted": false,
"dmca": false,
"locked": false
}
}
],
"pagination": {
"total": 1,
"limit": 25,
"offset": 0,
"has_more": false
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
results | array | Matching nodes, ordered by relevance score (highest first) |
results[].node_id | string | OpaqueId of the matching node |
results[].score | number | Relevance score for the match |
results[].template_ids | array of string | Templates contributing to the matching values for this node. Custom (template-independent) values do not appear here. |
results[].node | object | Standard node resource (same shape as storage list/details) |
pagination.total | int | Total number of matching nodes |
pagination.has_more | bool | true when more results exist past the current window |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | q missing/blank, template_id malformed, or a request beyond the supported result window |
1680 (Access Denied) | 403 | Caller is not a member of the workspace |
1654 (Internal Error) | 500 | Search backend transient failure |
Notes:
- Returns at most one entry per node, even when multiple metadata fields on that node matched.
- Both lexical match and prefix/wildcard match are supported (e.g.,
inv*matchesinvoice). - Indexing is event-driven — newly written or updated metadata typically becomes searchable within a few seconds.
- Binary metadata values are not indexed; only textual values (including JSON leaves) participate in matching.
- Each indexed document is bounded; metadata values exceeding the per-node ceiling are truncated for indexing only — source data is unaffected.
- This endpoint is independent of
/storage/search/— that endpoint searches by filename, this one searches by metadata field values.
Unified Search (Grouped by Type)
/current/workspace/{workspace_id}/search/
/current/share/{share_id}/search/
One search call across everything in a workspace or share, with results grouped by type into buckets. Instead of calling the per-type search endpoints separately, you issue a single query and get back a set of buckets — each with its own results and its own pagination. A workspace search returns up to four buckets: files, metadata, comments, and workflows. A share search returns the subset that applies to shares (typically files, plus comments when commenting is enabled on the share; metadata is workspace-only).
Each bucket is independently paginated and independently health-reported, so a transient problem affecting one bucket never blocks the others — that bucket comes back degraded with an empty result set while the rest return normally.
Auth required. Permission: View (workspace), search + file view permissions (share). All buckets are permission-filtered — see Permission Model below.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| search | string | Yes | — | Search query string (max 1024 characters; an empty value is rejected). |
| files_offset | int | No | 0 | Result offset for the files bucket. |
| files_limit | int | No | 25 | Page size for the files bucket. |
| metadata_offset | int | No | 0 | Result offset for the metadata bucket (workspace only). |
| metadata_limit | int | No | 25 | Page size for the metadata bucket (workspace only). |
| comments_offset | int | No | 0 | Result offset for the comments bucket. |
| comments_limit | int | No | 25 | Page size for the comments bucket. |
| workflows_offset | int | No | 0 | Result offset for the workflows bucket. |
| workflows_limit | int | No | 25 | Page size for the workflows bucket. |
Pagination is per bucket: files_offset/files_limit page the files bucket, comments_offset/comments_limit page the comments bucket, and so on. Each pair is optional; an omitted offset defaults to 0 and an omitted limit to 25. A request beyond the supported result window for any bucket returns a 400 input error. Every applicable bucket is always searched (the share endpoint omits metadata).
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/search/?search=quarterly+report&files_limit=10&comments_limit=5" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"buckets": {
"files": {
"items": [
{
"node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"name": "Q4 Report.pdf",
"parent_id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"type": "file",
"relevance_score": 0.93,
"content_snippet": "Quarterly revenue grew 18% year-over-year …",
"match_source": "both",
"media_segment": null,
"page": { "start_page": 3, "end_page": 3 },
"mimetype": "application/pdf",
"updated": "2026-04-22 09:30:55 UTC"
}
],
"offset": 0,
"limit": 10,
"total": 1,
"total_relation": "eq",
"has_more": false,
"status": "ok"
},
"metadata": {
"items": [
{
"node_id": "f7kp2-aaaaa-bbbbb-ccccc-ddddd-eeee",
"name": "Invoice-2026-0042.pdf",
"parent_id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz1",
"type": "file",
"relevance_score": 4.215,
"template_ids": ["mt_abc123def456"],
"updated": "2026-04-22 09:30:55 UTC"
}
],
"offset": 0,
"limit": 25,
"total": 1,
"total_relation": "eq",
"has_more": false,
"status": "ok"
},
"comments": {
"items": [
{
"comment_id": "c9xy1-zzzzz-yyyyy-xxxxx-wwwww-vvvv",
"entity": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"author_profile_id": "1234567890123456789",
"snippet": "Can we double-check the Q4 totals …",
"reference_type": null,
"relevance_score": 2.118,
"created": "2026-04-21 08:02:11 UTC",
"updated": "2026-04-21 08:02:11 UTC"
}
],
"offset": 0,
"limit": 5,
"total": 1,
"total_relation": "eq",
"has_more": false,
"status": "ok"
},
"workflows": {
"items": [
{
"entity_type": "task",
"entity_id": "t4aa0-bbbbb-ccccc-ddddd-eeeee-ffff",
"relevance_score": 1.904,
"title": "Review Q4 report",
"description": "Confirm the revenue figures before sign-off …",
"status": "open",
"updated": "2026-04-21 12:30:00 UTC"
}
],
"offset": 0,
"limit": 25,
"total": 1,
"total_relation": "eq",
"has_more": false,
"status": "ok"
}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
buckets | object | Map of bucket type → bucket object. Only applicable buckets are present (e.g. no metadata on a share). |
buckets.{type}.items | array | Result items for this bucket, ordered by relevance (relevance_score descending). Item shape is type-specific — see Bucket Item Shapes below. |
buckets.{type}.offset | int | The offset applied to this bucket. |
buckets.{type}.limit | int | The page size applied to this bucket. |
buckets.{type}.total | int | Number of matching, permission-visible items. This count is computed after permission filtering — it reflects what you can actually see, not raw index hits. |
buckets.{type}.total_relation | string | eq when total is exact within the searched window, or gte when it is a lower bound (more visible matches may exist beyond the searched window). |
buckets.{type}.has_more | bool | true when more results exist past the current page. |
buckets.{type}.status | string | ok for a healthy bucket, or degraded when the backend behind that bucket was temporarily unavailable (the bucket returns an empty items array but is still present so you can tell a backend hiccup from a bucket that does not apply). |
Bucket Item Shapes
Every item carries a relevance_score (higher is more relevant) and an updated timestamp. Beyond that, fields are type-specific:
files—node_id,name,parent_id,type, plus (when workspace/share intelligence is enabled) the hybrid-match fieldscontent_snippet(the matching text,nullfor keyword-only matches),match_source(keyword/semantic/both),media_segment({start_seconds, end_seconds}for audio/video, elsenull),page({start_page, end_page}for paginated documents, elsenull), andmimetype.metadata(workspace only) —node_id,name,parent_id,type, andtemplate_ids(the templates contributing the matched values).comments—comment_id,entity(the commented-on node/container),author_profile_id,snippet(the matching comment text),reference_type(the anchor type when the comment is anchored to a position in a file, elsenull),created.workflows—entity_type(task/review_decision),entity_id, and per-type body fields:title+description+statusfor tasks;comment_text+action+status(+workflow_id/workspace_id) for review decisions.
Permission Model
Every bucket is permission-filtered against the same rules as that type's dedicated endpoint. Results are produced by reading each match from the source of truth and re-checking the caller's permission before any content or count is returned, so search can never reveal an item, a snippet, or even a count for something the caller is not allowed to see. In particular:
- files / metadata — filtered by the caller's file view permissions (and, on a share, the share's per-item file-view rules).
- comments — only comments the caller can see in that workspace or share. On a workspace, the comments bucket is owner-inclusive of File Shares: it also returns comments left through the workspace's own live (active, non-expired, non-revoked) File Shares (a File Share is a view of a workspace file, so the comments belong to the same node). This is one-directional — workspace members see File Share comments, but File Share recipients never see the workspace's internal comments. Share search is unaffected. In rare cases (a workspace with a very large number of File Shares, or a transient error while enumerating them) workspace comment search may temporarily cover only the workspace's own comments; the File Share comments reappear on a later retry.
- workflows — workflow items (tasks and review decisions) the caller is allowed to observe. On a share, only tasks the caller can view are returned; review decisions have no share surface.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 400 | search missing/blank/too long, or a bucket request beyond the supported result window |
1609 (Not Found) | 404 | Search not available for workspace-backed shares (shared folders); share only |
1680 (Access Denied) | 403 | No search permission |
Notes:
- Search is not available for workspace-backed shares (shared folders) — the share endpoint returns
404in that case. - Comments and workflow items become searchable shortly after they are created or updated (indexing is asynchronous — typically within a few seconds).
- A
degradedbucket is safe to retry; the rest of the response is still valid. - This unified endpoint composes the same per-type searches as
/storage/search/,/metadata/search/, and the comments / workflow surfaces — use it when you want a single grouped result set, or the per-type endpoints when you only need one kind of result.
QuickShare (Workspace Only)
Deprecated — use File Share. The POST (create / extend-expiry) path now returns 10756 (Quickshare Deprecated) (403) with a directed message pointing to POST /current/workspace/{workspace_id}/create/fileshare/. The durable File Share replaces it. GET (details), DELETE (revoke), and the public read endpoints below remain live during the drain so existing links keep serving and can be torn down.
/current/workspace/{workspace_id}/storage/{node_id}/quickshare/
/current/workspace/{workspace_id}/storage/{node_id}/quickshare/
/current/workspace/{workspace_id}/storage/{node_id}/quickshare/
Retrieve or delete an existing temporary public link for a single file. Creation is deprecated (POST returns 403).
Auth required. Permission: Member.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit workspace profile ID |
| {node_id} | string | Yes | File OpaqueId |
POST — Create or Update QuickShare (deprecated → 403)
This path is deprecated and returns 10756 (Quickshare Deprecated) (403) for both create and extend-expiry. Use POST /current/workspace/{workspace_id}/create/fileshare/ (see File Share). The legacy expires / expires_at request parameters no longer apply because creation is closed.
GET — Get QuickShare Details
Returns the same format as POST. Returns 1609 (Not Found) (404) if no quickshare exists.
DELETE — Delete QuickShare
Returns {"result": true}. Returns 1609 (Not Found) (404) if no quickshare exists.
Public Access Endpoints (No Auth Required)
Once a quickshare is created, these endpoints are accessible without authentication:
GET /current/quickshare/{quickshare_id}/details/ -- metadata and file info
GET /current/quickshare/{quickshare_id}/storage/read/ -- download the file
GET /current/quickshare/{quickshare_id}/storage/preview/{preview_type}/read/ -- preview
GET /current/quickshare/{quickshare_id}/storage/preview/{preview_type}/read/file/{filename} -- preview sub-file
List QuickShares in Workspace
/current/workspace/{workspace_id}/storage/quickshares/list/
Returns an array of all active quickshares in the workspace.
Auth required. Permission: Member.
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/quickshares/list/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"quickshares": [
{
"id": "qs_abc123def456",
"node": { "id": "...", "type": "file", "name": "presentation.pdf" },
"creator_uid": { "id": "...", "email_address": "john@example.com" },
"limit_exceeded": false,
"expires": "2025-01-22 10:30:00 UTC",
"created": "2025-01-15 10:30:00 UTC"
}
]
}
File Share Read Endpoints (Public)
A File Share is the durable successor to QuickShare — a long-lived, link-shareable view of one workspace file. These public read endpoints serve the link viewer. They are anonymous-allowed where the access tier (anyone_with_link) permits; for any_registered / named_people the caller must present a bearer token and have sufficient access. A link password, if set, is presented via the x-ve-password request header (never in the URL). The bound file is read from the File Share record, so a caller can never substitute a different node id. Bandwidth is metered to the owning organization (no per-link transfer cap). Management endpoints (create / list / update / delete / grants) are in the Workspaces reference.
GET /current/fileshare/{fileshare_id}/details/ -- viewer metadata + bound file info
GET /current/fileshare/{fileshare_id}/storage/metadata/details/ -- the bound file's template + custom metadata fields (view capability)
GET /current/fileshare/{fileshare_id}/storage/read/ -- download the bound file (download capability)
GET /current/fileshare/{fileshare_id}/storage/preview/{preview_type}/read/ -- preview (view capability)
GET /current/fileshare/{fileshare_id}/storage/preview/{preview_type}/read/{download_token}/file/{filename} -- preview sub-file
GET /current/fileshare/{fileshare_id}/storage/versions/ -- list the bound file's version history (view capability)
GET /current/fileshare/{fileshare_id}/storage/versions/{version_id}/read/ -- download a specific version (download capability)
Details Response
{
"result": true,
"fileshare": {
"fileshare": "1234567890123456789",
"title": "Quarterly Presentation",
"access_option": "anyone_with_link",
"has_password": false,
"effective_capability": "download",
"file": {
"id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"type": "file",
"name": "presentation.pdf",
"parent": "a1b2c-3d4e5-6f7g8-9h0i1-2j3k4-lmno",
"version": "3u6cr-vxmyl-4y2pr-5jboz-afoke-k4s5",
"created": "2026-01-28 10:00:00 UTC",
"modified": "2026-01-28 12:30:00 UTC",
"size": 5242880,
"hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"hash_algo": "sha256",
"mimetype": "application/pdf",
"mimecategory": "document",
"previews": { "pdf": { "state": "ready" }, "thumbnail": { "state": "ready" } },
"virus": { "status": "scanned", "infected": false },
"summary": { "title": "Quarterly Presentation", "short": "Q4 results deck", "long": "..." },
"metadata": { "title": null, "short": null }
}
}
}
The bound file object carries the same fields as a workspace file-node detail (see Node Details in this reference): a viewer of the share sees that one file's full info — versioning, previews, summary, metadata, and provenance — exactly as it appears in the workspace. The share-level effective_capability (view / download / edit) reflects the highest capability the access tier / grant / password admit for the calling viewer.
Metadata Response
GET /current/fileshare/{fileshare_id}/storage/metadata/details/ returns the bound file's template + custom metadata key-value fields — the same template_metadata / custom_metadata shape as the workspace metadata endpoint, so a viewer sees that one file's metadata exactly as it appears in the workspace info panel. View capability; the node is the bound file (never a caller-supplied node id).
{
"result": true,
"object_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"template_id": "tmpl4-q2w3e-r5t6y-u7i8o-p9a0s-dfgh",
"node_id": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf", "type": "file", "name": "presentation.pdf", "parent": "a1b2c-3d4e5-6f7g8-9h0i1-2j3k4-lmno", "mimetype": "application/pdf" },
"template_metadata": [
{ "key": "department", "description": "Owning department", "type": "string", "value": "Finance", "is_auto": false, "updated": "2026-01-28 12:30:00 UTC" }
],
"custom_metadata": [
{ "key": "reviewed_by", "description": null, "type": "string", "value": "jdoe", "is_auto": false, "updated": "2026-01-28 12:31:00 UTC" }
],
"autoextractable": true
}
When the bound file carries no metadata (or the owning organization has no metadata feature), template_metadata and custom_metadata are empty arrays and template_id is null.
Preview types match the storage preview surface (thumbnail, image, pdf, mp4, hls_stream, etc.); multi-file previews (e.g. HLS) return a 307 Temporary Redirect to a sub-file endpoint. Version listing is read-only — there is no restore/promote on the public surface; each entry carries the same per-version fields as a workspace version listing.
Error Responses
| HTTP Status | Code | Cause |
|---|---|---|
| 400 | 1605 (Invalid Input) | Invalid File Share id |
| 401 | 1650 (Authentication Invalid) | A link password is required and was missing or wrong (present it via the x-ve-password header) |
| 403 | 1700 (Forbidden) | The access tier or named grant does not permit the caller |
| 404 | 1609 (Not Found) | No such File Share, or the bound file content is no longer available |
File Locking
Lock a file to prevent concurrent edits. Locks expire automatically if not renewed via heartbeat. Available on both workspace and share storage.
Acquire Lock
/current/workspace/{workspace_id}/storage/{node_id}/lock/
/current/share/{share_id}/storage/{node_id}/lock/
Acquire a lock on a file.
Auth required. Permission: Guest (workspace), file modification permission (share).
Request Body (form-encoded)
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
| duration | integer | No | 60-3600 seconds | Lock duration (default varies) |
| client_info | string | No | JSON object | Client metadata: device_name (max 255), client_version (max 50) |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/lock/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'duration=300' \
-d 'client_info={"device_name":"My Laptop","client_version":"2.1.0"}'
Response
{
"result": true,
"lock_token": "unique_lock_token_string",
"locked_at": "2025-01-28 10:00:00 UTC",
"expires_at": "2025-01-28 10:05:00 UTC",
"node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf"
}
Response Fields
| Field | Type | Description |
|---|---|---|
lock_token | string | Token required for heartbeat and release operations |
locked_at | string | Lock acquisition time (YYYY-MM-DD HH:MM:SS UTC) |
expires_at | string | Lock expiration time (YYYY-MM-DD HH:MM:SS UTC) |
node_id | string | OpaqueId of the locked node |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found or outside your workspace/share scope |
1609 (Not Found) | 404 | Cannot lock a deleted node (workspace only) |
1660 (Conflict) | 409 | Node already locked by another user |
1680 (Access Denied) | 401 | Insufficient permission to acquire a lock on this node (share only) |
1693 (Temporarily Unavailable) | 503 | Lock service momentarily unavailable; retry after a brief delay |
Heartbeat (Extend Lock)
/current/workspace/{workspace_id}/storage/{node_id}/lock/heartbeat/
/current/share/{share_id}/storage/{node_id}/lock/heartbeat/
Extend the lock duration.
Auth required. Permission: Guest (workspace), file modification permission (share).
Request Body (form-encoded)
| Parameter | Type | Required | Description |
|---|---|---|---|
| lock_token | string | Yes | Token from acquire response |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/lock/heartbeat/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'lock_token=unique_lock_token_string'
Response
{
"result": true,
"expires_at": "2025-01-28 10:10:00 UTC",
"time_remaining": 300
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | No lock exists on this node |
1609 (Not Found) | 404 | Lock has expired |
1609 (Not Found) | 404 | Node not found or outside your workspace/share scope |
1660 (Conflict) | 409 | Recreation race: another caller acquired the lock first; re-acquire and retry |
1680 (Access Denied) | 401 | Lock token does not match |
1680 (Access Denied) | 401 | Insufficient permission to heartbeat this lock (share only) |
1693 (Temporarily Unavailable) | 503 | Lock service momentarily unavailable; retry after a brief delay |
Notes:
- Send heartbeats well before the lock expires (e.g., at 50% of lock duration).
- Heartbeats can recreate an expired lock if the token and user match.
Release Lock
/current/workspace/{workspace_id}/storage/{node_id}/lock/
/current/share/{share_id}/storage/{node_id}/lock/
Release a lock on a file.
Auth required. Permission: Guest (workspace), file modification permission (share).
Request Body/Query
| Parameter | Type | Required | Description |
|---|---|---|---|
| lock_token | string | Yes | Token from acquire response |
Response
{
"result": true,
"released": true
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | No lock exists on this node |
1609 (Not Found) | 404 | Node not found or outside your workspace/share scope |
1680 (Access Denied) | 401 | Lock token does not match |
1680 (Access Denied) | 401 | Insufficient permission to release this lock (share only) |
1693 (Temporarily Unavailable) | 503 | Lock service momentarily unavailable; retry after a brief delay |
Lock Status
/current/workspace/{workspace_id}/storage/{node_id}/lock/
/current/share/{share_id}/storage/{node_id}/lock/
Check the lock status of a node.
Auth required. Permission: Guest (workspace), file view permission (share).
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Node not found or outside your workspace/share scope |
1680 (Access Denied) | 401 | Insufficient permission to view lock status (share only) |
Response (locked)
{
"result": true,
"locked": true,
"locked_at": "2025-01-28 10:00:00 UTC",
"expires_at": "2025-01-28 10:05:00 UTC",
"node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
"time_remaining": 245,
"locker_uid": "1234567890123456789"
}
Response (unlocked)
{
"result": true,
"locked": false
}
Previews
File previews provide rendered views of documents, images, video, and other content without downloading the original file.
Preview Types
| Value | Description |
|---|---|
thumbnail | Small thumbnail image |
image | Full-size image preview |
mp4 | MP4 video preview (transcoded) |
hlsstream | HLS video/audio stream |
audio | Audio preview (transcoded) |
pdf | PDF document preview |
spreadsheet | Spreadsheet preview |
bin | Binary preview (raw bytes for clients that render their own preview) |
Preview States
Returned in node details responses under previews.{type}.state:
| State | Description |
|---|---|
unknown | Preview status not yet determined |
not possible | File type cannot be previewed |
not generated | Preview not yet generated |
error | Preview generation failed |
in progress | Preview is being generated |
ready | Preview is available |
Preauthorize Preview
/current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/preauthorize/
/current/share/{share_id}/storage/{node_id}/preview/{preview_type}/preauthorize/
Get a preview download URL with an embedded token.
Auth required. Permission: View (workspace), file view permission (share).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {preview_type} | string | Yes | One of: thumbnail, image, mp4, hlsstream, audio, pdf, spreadsheet, bin |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/preview/thumbnail/preauthorize/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"downloadToken": "eyJhbGciOiJIUzI1NiJ9...",
"path": "/current/workspace/.../preview/thumbnail/read/eyJhbGci.../file/preview.png",
"primaryFilename": "preview.png"
}
Response Fields
| Field | Type | Description |
|---|---|---|
downloadToken | string | JWT token for preview access |
path | string | Full API path to read the preview file |
primaryFilename | string | Name of the primary preview file |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | File not found |
1605 (Invalid Input) | 400 | Can only preview file or note |
1609 (Not Found) | 404 | File is in trash |
1652 (Resource Not Found) | 404 | File content is no longer available |
1652 (Resource Not Found) | 404 | Preview not available |
Read Preview
/current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/read/
/current/share/{share_id}/storage/{node_id}/preview/{preview_type}/read/
Read or redirect to a preview. For single-file previews, streams content directly. For multi-file previews, returns a 307 Temporary Redirect to the file-specific endpoint with a generated token.
If the source file is corrupt, truncated, or otherwise unreadable by the render pipeline, returns HTTP 422 Unprocessable Entity. Clients should not retry — the source file itself is the problem.
Auth required.
Token-Based Preview Read
/current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/read/{token}/file/{filename}
/current/share/{share_id}/storage/{node_id}/preview/{preview_type}/read/{download_token}/file/{filename}
Read a specific preview file using a token (from preauthorize). Token-based auth — no Authorization header needed.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {token} | string | Yes | Download token from preauthorize |
| {filename} | string | Yes | Preview filename |
Transforms
Image transforms allow on-the-fly resizing, cropping, rotating, and format conversion.
Get Transform Status
/current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/
/current/share/{share_id}/storage/{node_id}/transform/{transform_name}/
Check if a transformation is available without triggering it.
Auth required. Currently the supported {transform_name} is image.
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/transform/image/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"state": "rendered"
}
Transformation States
| State | Description |
|---|---|
rendered | Transform is ready |
rendering | Transform in progress |
unrendered | Transform not yet requested |
unable to render | Transform failed or unsupported |
Request Transform
/current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/request/
/current/share/{share_id}/storage/{node_id}/transform/{transform_name}/request/
Request a transformation. If not yet rendered, triggers the transformation. If already rendered, returns immediately.
Auth required.
Response
{
"result": true,
"state": "rendered"
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1609 (Not Found) | 404 | Unknown transformation name |
1609 (Not Found) | 404 | Unable to transform (failed or unsupported) |
Read Transformed File
/current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/read/
/current/share/{share_id}/storage/{node_id}/transform/{transform_name}/read/
Download the transformed file. Supports byte-range requests and token auth.
Auth: JWT or download token.
Image Transform Parameters
Pass as query parameters on transform read endpoints:
| Parameter | Type | Values |
|---|---|---|
| output-format | string | png, jpg |
| width | int | Target width in pixels |
| height | int | Target height in pixels |
| cropwidth | int | Crop region width |
| cropheight | int | Crop region height |
| cropx | int | Crop region X offset |
| cropy | int | Crop region Y offset |
| rotate | int | 0, 90, 180, 270 |
| size | string | Predefined: IconSmall, IconMedium, Preview |
curl Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf/transform/image/read/?width=200&height=200&output-format=jpg" \
-H "Authorization: Bearer {jwt_token}" \
-o thumbnail.jpg
Request Transform Download Token
/current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/requestread/
/current/share/{share_id}/storage/{node_id}/transform/{transform_name}/requestread/
Get a temporary download token for the transformed file. Available on both workspaces and shares.
Auth required.
Response
{
"result": true,
"token": "eyJhbGciOiJIUzI1NiJ9..."
}
Download Tokens Pattern
The requestread endpoint generates temporary, auth-free download tokens for files, previews, and transforms.
Flow:
GET .../storage/{node_id}/requestread/— returns{"token": "..."}GET .../storage/{node_id}/read/?token={token}— download without Authorization header
Useful for opening files in browser tabs or embedding in pages without exposing auth headers.
In medium security mode, file previews are available for guests but direct downloads are restricted. Owners and admins can still download normally.
Metadata Saved Views
Each user can persist per-template column order, column visibility, and sort preferences. Saved views are private to the user — two users can independently customize the same template. Identity is the tuple (workspace, user, template); there is at most one saved view per user per template per workspace.
/current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
/current/workspace/{workspace_id}/metadata/view/
/current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
/current/workspace/{workspace_id}/metadata/views/
/current/workspace/{workspace_id}/metadata/view/{template_id}/export/
Retrieve, upsert, delete, list, or export the caller's saved views.
Auth required. Permission: Member. Metadata billing feature required.
GET — Retrieve Saved View
Returns the caller's saved view for the supplied template. Responds 1609 (Not Found) (404) when the caller has no view saved for that template.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| template_id | string | Yes | Template OpaqueId to resolve the view against. |
POST — Create or Update Saved View
Upserts the caller's saved view. If a view already exists for this template, the row is replaced in place (its id and created timestamp are preserved; updated is refreshed). Otherwise a new row is created.
Send as application/x-www-form-urlencoded. The config field holds a JSON-encoded string (not a nested JSON body) — sending Content-Type: application/json is rejected with 406 (This field is missing).
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| Content-Type | header | Yes | Must be application/x-www-form-urlencoded. application/json bodies are rejected. |
| template_id | string | Yes | Template OpaqueId the view belongs to. Accepted in the body or the query string. |
| config | string (URL-encoded JSON) | Yes | Form-encoded field whose value is a JSON-serialized string of the view's config object. See Config Schema below for the JSON shape that must be serialized into this field. |
| name | string | No | Optional human label for the view (max 30 chars). Preserved on upsert if omitted. The view id is server-owned and never client-supplied. |
Example
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/metadata/view/" \
-H "Authorization: Bearer {jwt_token}" \
--data-urlencode "template_id={template_id}" \
--data-urlencode 'config={"version":1,"columns":[{"field":"title","visible":true,"width":180}],"sort":{"field":"updated","dir":"desc"},"filters":[]}'
DELETE — Remove Saved View
Deletes only the caller's own view; other users' views are untouched. Responds 1609 (Not Found) (404) when the caller has no view saved for that template.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| template_id | string | Yes | Template OpaqueId whose saved view should be removed. |
GET Views — List Saved Views
Lists every saved view the caller owns in this workspace (one entry per template the caller has customized). Views belonging to other users are never returned.
Config Schema
{
"version": 1,
"columns": [
{ "field": "title", "visible": true, "width": 180 },
{ "field": "status", "visible": true, "width": 120 }
],
"sort": { "field": "updated", "dir": "desc" },
"filters": [
{ "field": "status", "operator": "=", "value_type": "string", "value": "active" },
{ "field": "priority", "operator": ">=", "value_type": "int", "value": 3 }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
| version | int | Yes | Schema version. Must equal 1. |
| columns | array | Yes | Ordered list of column entries. The array order IS the display order. |
| columns[].field | string | Yes | Template field name. Non-empty. |
| columns[].visible | bool | No | Whether the column is visible. |
| columns[].width | int | No | Column width in pixels. Positive. |
| sort | object | Yes | Default sort for this view. |
| sort.field | string | Yes | Template field to sort by. |
| sort.dir | string | Yes | asc or desc. |
| filters | array | Yes | Ordered list of filter entries (may be empty). AND-chained; order preserved round-trip. See Filters below. |
| filters[].field | string | Yes | Template field name. Non-empty. Matches the naming used by columns[].field and sort.field. |
| filters[].operator | string | Yes | One of =, !=, <, <=, >, >=. |
| filters[].value_type | string | Yes | One of string, int, float, bool. json is rejected. |
| filters[].value | mixed | Yes | Non-null scalar matching value_type. Cannot be an array or object. |
Unknown top-level keys, unknown columns[] keys, unknown sort keys, or unknown filters[] keys cause the request to be rejected with 1605 (Invalid Input).
Filters
Filter entries in a saved view use {field, operator, value_type, value} — the per-entry field name matches the naming used by columns[].field and sort.field for a uniform identifier across the config.
- Max 5 filter entries per view.
- AND-chained; insertion order is preserved round-trip.
- Numeric value types (
int,float) accept the stringy form ("42") via numeric validation. - For relational operators (
<,<=,>,>=) withvalue_type: string, the server performs lexical comparison. Numeric value types use numeric comparison.
Value coercion. Values follow standard scalar coercion — strings like "true", "1", "yes", "on" (and their falsy counterparts) are accepted when value_type is bool; numeric-form strings like "42" or "1.5" are accepted when value_type is int or float. Sending the typed value directly (e.g. 3 instead of "3") is preferred and round-trips unchanged.
Stateless filters query parameter shape. The template nodes listing endpoint accepts a stateless filters query parameter whose per-entry identifier is still key (not field); that pre-existing query-layer contract is unchanged. Saved View configs use field for internal consistency with columns[].field and sort.field. When the server auto-applies a saved view on the nodes endpoint, it handles the translation internally. Use field when writing a Saved View; use key only when passing a stateless filters query parameter directly on the nodes endpoint.
Response Shape
{
"result": true,
"id": "{view_id}",
"user_id": "{user_id}",
"template_id": "{template_id}",
"name": "{name}",
"config": { "version": 1, "columns": [], "sort": { "field": "updated", "dir": "desc" }, "filters": [] },
"created": "2026-04-23 11:02:00 UTC",
"updated": "2026-04-23 11:02:00 UTC"
}
The list endpoint wraps rows under response.items[] and adds a response.count field.
POST Export — Export Saved View
Exports the caller's saved view for the given template to a TSV file written into workspace storage. The template_id is a path segment (before /export/), not a body or query field. The export runs asynchronously: the call queues a job and returns immediately — poll the destination folder for the finished file.
Precondition. A saved view must already exist for that template, otherwise the call responds 1609 (Not Found) (404).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| template_id | string | Yes | Template OpaqueId whose saved view is exported. Supplied as a URI path segment before /export/. |
Request Body
Send as application/x-www-form-urlencoded. Both fields are optional.
| Parameter | Type | Required | Description |
|---|---|---|---|
| parent_node_id | string | No | Destination folder OpaqueId (max 64 chars). Omit to write to the workspace root. |
| filename | string | No | Output filename (max 200 chars). |
Deduplication. If an export for the same (workspace, user, template, parent_node_id) is already in flight, the call returns {"status": "duplicate", "filename": "...", "message": "..."} with no job_id instead of queuing a second job.
Queued Response
{
"result": true,
"job_id": "{job_id}",
"status": "queued",
"filename": "{filename}",
"message": "Export queued"
}
Exports are capped at roughly 50,000 rows.
Auto-Apply on Template Nodes Listing
GET /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes inspects the caller's saved view for the given template.
- Sort. When the request does not include an explicit
sort_fieldand the caller has a saved view whose config carries asort.field, the server appliessort.field+sort.dirto the listing automatically. Passingsort_fieldin the query always wins. - Filters. When the request does not include a
filtersquery parameter and the caller's saved view carries a non-emptyfiltersarray, the server applies those filters to the listing automatically. Passingfiltersexplicitly (including an empty string or empty array) always wins and bypasses the saved view — no server-side merge is performed. - Partial override.
sort_fieldandfiltersfall back independently. A request that suppliessort_fieldbut omitsfiltersuses the caller's sort with the saved view's filters (and vice versa). Callers that want a stateless listing with neither the saved sort nor the saved filters must supply both explicitly.
Column order and visibility are not applied server-side — clients use the saved view's columns array to decide which columns to render.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Missing or malformed parameters, or config failed validation (unknown keys, bad types, bad operator / value_type, or more than 5 filters). Also returned when the body is sent as application/json instead of form-encoded. |
1609 (Not Found) | 404 | Caller has no saved view for this template (GET / DELETE / export) |
1664 (Datastore Error) | 500 | Failed to read or persist the view |
1680 (Access Denied) | 403 | Not a workspace member or metadata feature not enabled |
Workspace-Only Features
These endpoints are only available on workspaces, not shares:
addlink— add a share link to storagecreatenote/updatenote— markdown note creation and editingreadnoteis available on both workspaces and shares (shares support token-based access)quickshare— temporary public file linksquickshares/list— list all quickshares
Share-Specific Notes
- Share storage follows identical patterns to workspace storage for all common operations
- Shares support file locking (acquire, heartbeat, release, status)
- Shares support previews and transforms (status, request, read, requestread)
- Shares additionally support
requestpreview(one-time preview nonce) fordownload_security=mediumshares - Share permissions are granular: separate permissions for file view, download, creation, modification, and administration
- Shares may restrict operations to files the user created (creator-only restrictions)
- Workspace folder shares map
rootto the designated folder and scope all operations to that subtree - Search is not available for workspace folder shares (files are indexed by workspace, not share)
- Public shares may allow listing and downloading without JWT authentication