{
  "openapi": "3.1.0",
  "info": {
    "title": "Folderbird API",
    "version": "1.0",
    "summary": "Browse, search, read, download, upload and run commands on your own PCs through Folderbird.",
    "description": "Folderbird lets a user reach their own Windows or Linux PC from anywhere. The user installs the Folderbird app on the PC; this API then relays each request to that PC over the app's live connection and returns the PC's answer. Files are never synced and never stored: a download or upload passes through Folderbird's transfer storage and the transient copy is deleted afterwards. Every request is authorised by a scoped API key the user created (scopes: devices:read, browse, download, upload, exec, devices:enroll), optionally bound to specific devices, rate-limited per key, and every command is written to the user's audit trail. Calls that need the PC wait for it synchronously (30-120 s) and return 504 if it is offline.",
    "termsOfService": "https://folderbird.com/terms",
    "contact": { "name": "Folderbird support", "url": "https://folderbird.com/Contact", "email": "info@folderbird.com" },
    "x-privacy-policy": "https://folderbird.com/privacy",
    "x-documentation": "https://folderbird.com/Integrations/Api"
  },
  "servers": [ { "url": "https://folderbird.com/api/v1", "description": "Folderbird" } ],
  "security": [ { "ApiKeyBearer": [] }, { "ApiKeyHeader": [] } ],
  "tags": [
    { "name": "devices", "description": "The user's registered PCs" },
    { "name": "files", "description": "Browse, search, read and transfer files on a PC" },
    { "name": "commands", "description": "Run shell commands on a PC" }
  ],
  "paths": {
    "/devices": {
      "get": {
        "operationId": "listDevices",
        "tags": ["devices"],
        "summary": "List the user's devices",
        "description": "Returns every device the key may see (all of the user's devices, or only the ones the key is bound to), with online status, last-seen text and the Folderbird app version. Does not contact any PC. Scope: devices:read.",
        "x-openai-isConsequential": false,
        "responses": {
          "200": { "description": "Device list", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceList" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/devices/code": {
      "post": {
        "operationId": "createDeviceEnrollmentCode",
        "tags": ["devices"],
        "summary": "Mint a one-time device enrollment code",
        "description": "Creates a 10-minute one-time code that, together with the account password, lets the Folderbird app on a NEW PC join this account. Only accounts with two-factor authentication are ever asked for the code at device sign-in (`required` says so). Scope: devices:enroll — never granted implicitly; the user must tick it when creating the key.",
        "x-openai-isConsequential": true,
        "responses": {
          "200": { "description": "Code minted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnrollmentCode" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/devices/{id}/exec": {
      "post": {
        "operationId": "runCommand",
        "tags": ["commands"],
        "summary": "Run a shell command on a PC",
        "description": "Runs the command on the PC with the permissions of the user signed in there (cmd or PowerShell on Windows; sh on the Linux build, where `shell` is ignored) and returns its combined stdout/stderr. This can change or delete anything that user can. The command is written to the account's audit trail BEFORE it is sent, and the call waits up to 45 s for the PC. Scope: exec. Rate limit: 20 per minute per key. A command that would delete, move, overwrite, format or change system state (del, rm, Remove-Item, Move-Item, format, diskpart, shutdown, Stop-Process, ...) is refused with 409 and NOT run unless `confirmDestructive` is true; the refusal is recorded in the audit trail. Confirm the exact command with the user before setting it.",
        "x-openai-isConsequential": true,
        "parameters": [ { "$ref": "#/components/parameters/DeviceId" } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecRequest" } } } },
        "responses": {
          "200": { "description": "Command output", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/DestructiveRefused" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "504": { "$ref": "#/components/responses/DeviceTimeout" }
        }
      }
    },
    "/devices/{id}/files": {
      "get": {
        "operationId": "listFolder",
        "tags": ["files"],
        "summary": "List a folder on a PC",
        "description": "Asks the PC for the sub-folders and files at `path` and returns them (names, sizes, last-modified text). Read-only. Waits up to 30 s. Scope: browse.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Folder path in the PC's own form: `C:\\Users\\Me` on Windows, `/home/me` on Linux. A bare drive letter such as `C:` means its root.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Folder listing", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FolderListing" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "504": { "$ref": "#/components/responses/DeviceTimeout" }
        }
      }
    },
    "/devices/{id}/search": {
      "get": {
        "operationId": "searchFiles",
        "tags": ["files"],
        "summary": "Search a PC for files",
        "description": "Searches by file name, or by file CONTENT when `content=true`. On Windows this queries the Windows Search index (fast, indexed locations only); on the Linux build it walks the folder with find/grep. Read-only. Waits up to 45 s. Scope: browse.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "q", "in": "query", "required": true, "description": "File-name fragment, or words to find inside files when content=true.", "schema": { "type": "string" } },
          { "name": "scope", "in": "query", "required": false, "description": "Limit the search to this folder subtree.", "schema": { "type": "string" } },
          { "name": "content", "in": "query", "required": false, "description": "true to search inside file contents instead of names.", "schema": { "type": "boolean", "default": false } }
        ],
        "responses": {
          "200": { "description": "Matches", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" }
        }
      }
    },
    "/devices/{id}/system": {
      "get": {
        "operationId": "getSystemInfo",
        "tags": ["devices"],
        "summary": "Health snapshot of a PC",
        "description": "OS, CPU %, RAM total/free, free disk per drive, uptime and the top memory-using processes, as JSON. Read-only. Waits up to 45 s. Scope: devices:read.",
        "x-openai-isConsequential": false,
        "parameters": [ { "$ref": "#/components/parameters/DeviceId" } ],
        "responses": {
          "200": { "description": "System information", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" }
        }
      }
    },
    "/devices/{id}/files/download": {
      "get": {
        "operationId": "downloadFile",
        "tags": ["files"],
        "summary": "Download a file from a PC",
        "description": "The PC sends the file to Folderbird's transfer storage; this call streams it back as `application/octet-stream` with a Content-Disposition file name. The transient copy is deleted about 10 minutes after the response. The PC is not changed. Waits up to 180 s for the file to land. Scope: download.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Full file path on the PC.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "The file's bytes", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" },
          "503": { "$ref": "#/components/responses/StorageUnavailable" }
        }
      }
    },
    "/devices/{id}/files/preview": {
      "post": {
        "operationId": "createPreviewLink",
        "tags": ["files"],
        "summary": "Stage a file and get a preview-page URL",
        "description": "The PC sends the file to transfer storage and the response carries a `preview_url` to a Folderbird page that renders PDF, images and Office documents (used by the mobile apps' in-app preview). The URL embeds a 15-minute token; the staged copy is deleted within 2 hours. The PC is not changed. Scope: download.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Full file path on the PC.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Preview link", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true, "properties": { "preview_url": { "type": "string", "format": "uri" } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" },
          "503": { "$ref": "#/components/responses/StorageUnavailable" }
        }
      }
    },
    "/devices/{id}/files/upload": {
      "post": {
        "operationId": "uploadFile",
        "tags": ["files"],
        "summary": "Send a file to a PC",
        "description": "Stages the uploaded file (multipart field `file`, up to 2 GB) in transfer storage; the PC pulls it and writes it into the target folder under its original file name. An existing file with the same name in that folder is OVERWRITTEN and Folderbird keeps no copy of the old version. The staged copy is deleted 2 minutes after the PC has pulled it. Waits up to 120 s. Scope: upload.",
        "x-openai-isConsequential": true,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Target FOLDER on the PC.", "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["file"], "properties": { "file": { "type": "string", "format": "binary" } } } } } },
        "responses": {
          "200": { "description": "Delivered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/StorageUnavailable" },
          "504": { "$ref": "#/components/responses/DeviceTimeout" }
        }
      }
    },
    "/devices/{id}/files/write": {
      "post": {
        "operationId": "writeTextFile",
        "tags": ["files"],
        "summary": "Create or overwrite a text file on a PC",
        "description": "Writes `content` as UTF-8 to the full file path in `path`. An existing file at that path is REPLACED and Folderbird keeps no copy of the old version; to edit, download or read first and send the whole new text. Waits up to 60 s. Scope: upload.",
        "x-openai-isConsequential": true,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Full destination file path on the PC, including the file name.", "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WriteRequest" } } } },
        "responses": {
          "200": { "description": "Delivered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WriteResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" }
        }
      }
    },
    "/devices/{id}/folders": {
      "post": {
        "operationId": "createFolder",
        "tags": ["files"],
        "summary": "Create a folder on a PC",
        "description": "Creates the folder at `path`, including any missing parents (like mkdir -p). Reports whether it already existed; never touches existing files. Waits up to 30 s. Scope: upload.",
        "x-openai-isConsequential": true,
        "parameters": [
          { "$ref": "#/components/parameters/DeviceId" },
          { "name": "path", "in": "query", "required": true, "description": "Full folder path to create.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateFolderResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/DeviceError" }
        }
      }
    },
    "/files/{token}": {
      "get": {
        "operationId": "redeemDownloadLink",
        "tags": ["files"],
        "summary": "Redeem a temporary download link",
        "description": "Streams a file that was previously staged by the MCP `download_file` tool. The 64-hex token IS the credential: it is bound to the account it was minted for, lives 15 minutes, and is revoked once the staged copy is gone. No API key is needed. Supports HTTP range requests.",
        "x-openai-isConsequential": false,
        "security": [],
        "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[0-9a-f]{64}$" } } ],
        "responses": {
          "200": { "description": "The file's bytes", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "502": { "$ref": "#/components/responses/DeviceError" },
          "503": { "$ref": "#/components/responses/StorageUnavailable" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Folderbird API key (`fbk_live_…`) as a bearer token. Create one at https://folderbird.com/Manage/ApiKeys, choosing the scopes (devices:read, browse, download, upload, exec, devices:enroll) and optionally the devices it may reach. Keys are stored hashed and can be revoked at any time."
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "The same Folderbird API key in an X-Api-Key header, for clients that cannot set Authorization."
      }
    },
    "parameters": {
      "DeviceId": { "name": "id", "in": "path", "required": true, "description": "Numeric device id from GET /devices.", "schema": { "type": "string" } }
    },
    "responses": {
      "BadRequest": { "description": "Missing or malformed parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing, invalid, expired or revoked API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "The key lacks the required scope, may not target this device, or the device is locked (over the plan's device limit)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Device not found (or not the caller's), or unknown/expired link", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooManyRequests": { "description": "Rate limit exceeded (60/min per key; 20/min for exec). The body is empty." },
      "ServerError": { "description": "Unexpected server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "DeviceError": { "description": "The PC answered with an error (file not found, permission denied, transfer failed) — `error` carries the PC's message with any credential redacted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "StorageUnavailable": { "description": "Transfer storage is not configured on this server", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "DeviceTimeout": { "description": "The PC did not answer in time — `error` says whether it is offline", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "DestructiveRefused": { "description": "The command looks destructive (delete, move, format, shutdown, process kill) and `confirmDestructive` was not true — it was NOT run. Ask the user to confirm the exact command, then re-send it with `confirmDestructive: true`. The refusal is recorded in the audit trail.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DestructiveRefusal" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "device": { "type": "string" },
          "path": { "type": "string" }
        },
        "required": ["error"]
      },
      "Device": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "description": "Use this as {id} in the other operations." },
          "name": { "type": "string", "description": "The PC's name as reported by the Folderbird app." },
          "status": { "type": "string", "description": "online, unstable, offline, or a finer connection state." },
          "lastSeen": { "type": "string", "description": "Human-readable last-seen text." },
          "appVersion": { "type": ["string", "null"], "description": "Folderbird app version on the PC, when known." }
        },
        "required": ["id", "name", "status"]
      },
      "DeviceList": {
        "type": "object",
        "properties": { "devices": { "type": "array", "items": { "$ref": "#/components/schemas/Device" } } },
        "required": ["devices"]
      },
      "EnrollmentCode": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "minutes": { "type": "integer", "description": "Validity in minutes (10)." },
          "required": { "type": "boolean", "description": "true only when the account has two-factor authentication, i.e. when a new PC will actually be asked for the code." }
        },
        "required": ["code", "minutes", "required"]
      },
      "ExecRequest": {
        "type": "object",
        "properties": {
          "command": { "type": "string", "description": "The full command line to run." },
          "shell": { "type": "string", "enum": ["cmd", "powershell"], "default": "cmd", "description": "Windows only; the Linux build always runs sh." },
          "confirmDestructive": { "type": "boolean", "default": false, "description": "Must be true for a command that deletes, moves, overwrites, formats or changes system state (del, rm, Remove-Item, Move-Item, format, diskpart, reg delete, schtasks /delete, Stop-Process, shutdown, ...). Set it only after the user has confirmed the exact command; without it such a command is refused with 409 and nothing runs." }
        },
        "required": ["command"]
      },
      "DestructiveRefusal": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Why it was refused, in plain words." },
          "code": { "type": "string", "enum": ["confirm_destructive_required"] },
          "device": { "type": "string" },
          "verb": { "type": "string", "description": "The token or shape that classed the command as destructive, e.g. `del`, `reg delete`, `::deletefile(`." }
        },
        "required": ["error", "code", "device", "verb"]
      },
      "ExecResponse": {
        "type": "object",
        "properties": {
          "device": { "type": "string" },
          "shell": { "type": "string" },
          "command": { "type": "string" },
          "output": { "type": "string", "description": "Combined stdout and stderr." }
        },
        "required": ["device", "shell", "command", "output"]
      },
      "FolderListing": {
        "type": "object",
        "properties": {
          "device": { "type": "string" },
          "path": { "type": "string" },
          "folders": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "path": { "type": "string" } }, "required": ["name", "path"] } },
          "files": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "size": { "type": ["integer", "string"] }, "lastmodified": { "type": "string" } }, "required": ["name"] } }
        },
        "required": ["device", "path", "folders", "files"]
      },
      "SearchResult": {
        "type": "object",
        "additionalProperties": true,
        "description": "Matching files with path, size and modified date; the exact shape follows the PC's search helper."
      },
      "WriteRequest": {
        "type": "object",
        "properties": { "content": { "type": "string", "description": "The full text to write (replaces the file)." } },
        "required": ["content"]
      },
      "WriteResult": {
        "type": "object",
        "properties": {
          "path": { "type": "string", "description": "Folder the file was written into." },
          "file": { "type": "string" },
          "bytes": { "type": "integer" },
          "delivered": { "type": "boolean" }
        },
        "required": ["path", "file", "bytes", "delivered"]
      },
      "UploadResult": {
        "type": "object",
        "properties": {
          "device": { "type": "string" },
          "path": { "type": "string", "description": "Target folder." },
          "file": { "type": "string" },
          "size": { "type": "integer" },
          "delivered": { "type": "boolean" }
        },
        "required": ["device", "path", "file", "size", "delivered"]
      },
      "CreateFolderResult": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "created": { "type": "boolean" },
          "existed": { "type": "boolean" }
        },
        "required": ["path", "created", "existed"]
      }
    }
  }
}