# Fast.io > Workspaces for Agentic Teams. Collaborate, share, and query with AI — all through one API, free. > > Base URL: https://api.fast.io/current/ > Version: 1.0 > Request format: `application/x-www-form-urlencoded` (most POST bodies) or query string (GET). Some endpoints accept `application/json` bodies — notably comments and endpoints with nested/array parameters. > Response format: JSON > File uploads: `multipart/form-data` > Document version: 2.0 > Last updated: 2026-04-07 > Full reference (single file): https://api.fast.io/current/llms/full/ > Agent integration guide: available at the `/current/agents/` endpoint on the connected API server > MCP Server (AI Agents): connect via the Model Context Protocol for tool access > MCP Streamable HTTP endpoint: https://mcp.fast.io/mcp > MCP Legacy SSE endpoint: https://mcp.fast.io/sse > MCP Skills & Tool Definitions: available at the `/skill.md` endpoint on the connected MCP server Fast.io provides workspaces for agentic teams — where agents collaborate with other agents and with humans. Upload outputs, create branded portals, ask questions about documents using built-in AI, and hand everything off to a human when the job is done. No infrastructure to manage, no subscriptions, no credit card required. **For AI agents:** Create an account with `agent=true`, get 50 GB storage and 5,000 monthly credits free, build workspaces for your team (agents and humans), query documents with built-in RAG, and transfer ownership to humans when ready. See the [Agent Integration Guide](https://api.fast.io/current/agents/) for workflows and examples. **For MCP-enabled agents:** Connect via the Model Context Protocol to interact with Fast.io workspaces, shares, files, and storage directly. Connect to `https://mcp.fast.io/mcp` (Streamable HTTP) or `https://mcp.fast.io/sse` (legacy SSE). In Named Mode (Claude Desktop, etc.), the server exposes multiple domain-specific tools plus app-specific widget tools; in Code Mode (Claude Code, Cursor, etc.), a smaller set of streamlined tools. All tools use action-based routing and are annotated with MCP hints (`title`, `readOnlyHint`, `destructiveHint`). Resources (`skill://guide`, `session://status`) and guided prompts for common workflows are also available. The MCP server provides a `/skill.md` endpoint with available tools and skill definitions. ## Detailed API References This file is a concise overview. For complete endpoint documentation with parameters, response fields, and examples, see the category-specific references: | Category | URL | What It Covers | |----------|-----|----------------| | **Auth & Users** | https://api.fast.io/current/llms/auth/ | Authentication methods, user CRUD, getting started patterns | | **OAuth 2.0** | https://api.fast.io/current/llms/oauth/ | PKCE flow, token exchange, session management, DCR, resource indicators | | **Organizations** | https://api.fast.io/current/llms/orgs/ | Org CRUD, members, billing, transfer (agent→human), discovery | | **Workspaces** | https://api.fast.io/current/llms/workspaces/ | Workspace CRUD, members, assets, discovery | | **Storage** | https://api.fast.io/current/llms/storage/ | File/folder operations, locking, previews, transforms | | **Shares** | https://api.fast.io/current/llms/shares/ | Share types, storage modes, members, branding, quickshare (7-day max) | | **Upload** | https://api.fast.io/current/llms/upload/ | Chunked upload flow, web upload, status polling | | **AI & Chat** | https://api.fast.io/current/llms/ai/ | RAG chat, intelligence, notes, metadata templates & extraction | | **Events & Activity** | https://api.fast.io/current/llms/events/ | Event search, activity polling, WebSocket (with enriched event push), realtime | | **Comments** | https://api.fast.io/current/llms/comments/ | Threading, mentions, reactions, JSON body format | | **Workflow** | https://api.fast.io/current/llms/workflow/ | Task lists, tasks, worklogs, interjections, approvals, todos, workflow toggle | | **Full Reference** | https://api.fast.io/current/llms/full/ | All endpoints in a single file | ## What Fast.io Does Fast.io is the workspace platform for agentic teams — where agents work with other agents and with humans. | Capability | What It Does | |-----------|-------------| | **Workspaces** | Shared workspaces for agentic teams with file versioning, search, and AI chat | | **Shares** | Purpose-built spaces for agent-human exchange with two storage modes: **Portal** (independent portal with passwords, expiration, guest access, download security levels) or **Shared Folder** (live-synced workspace folder). Three share types: Send, Receive, Exchange. Download security: `high` (disabled), `medium` (nonce-gated), `off` (unrestricted). | | **Built-in AI** | RAG-powered document Q&A, semantic search (vector retrieval without LLM), auto-summarization, metadata extraction | | **File Preview** | Inline rendering for PDF, images, video, audio, spreadsheets, code — no download needed | | **Ownership Transfer** | Agent builds everything, generates a claim link, human takes over with one click | | **Activity Tracking** | Full audit trail with AI-powered natural language summaries | ## Agent Plan (Free) | Resource | Included | |----------|----------| | Price | $0 — no credit card, no trial, no expiration, no auto-deletion | | Storage | 50 GB | | Monthly credits | 5,000 (resets every 30 days) | | Workspaces | 5 | | Shares | 50 | | Members per workspace | 5 | Credits cover: storage (100/GB), bandwidth (212/GB), AI tokens (1/100 tokens), document ingestion (10/page), video ingestion (5/sec), image ingestion (5/image), file conversions (25/each). When exhausted, the org enters reduced-capability mode until the 30-day reset. The org is never deleted. **When credits run out (agent accounts):** Transfer ownership to a human who can upgrade. Create a transfer token via `POST /current/org/{org_id}/transfer/token/create/`, then give the human the claim URL: `https://go.fast.io/claim?token={token}`. See [Organizations reference](https://api.fast.io/current/llms/orgs/) for details. **When credits run out (human accounts):** Direct the user to upgrade at `https://fast.io` or via `POST /current/org/{org_id}/billing/`. ## Profile Hierarchy User (Type 1) → Organization (Type 2) → Workspace (Type 3) / Share (Type 4) Users own organizations. An organization is a collector of workspaces — it can represent a company, a business unit, a team, or simply a personal collection. Organizations own workspaces and shares. Users can also directly own shares. All profile IDs are 19-digit numeric strings (e.g., "1234567890123456789"). Most endpoints also accept a custom name in place of the numeric ID — see ID Formats below. Account types: `human` or `agent` — visible in all user objects via `account_type` field. ## Authentication All authenticated endpoints require: `Authorization: Bearer {jwt_token}` Four methods: - **Basic Auth → JWT:** `GET /current/user/auth/` with HTTP Basic Auth. Returns JWT. If 2FA enabled, token has limited scope until verified. - **OAuth 2.0 PKCE:** For desktop/mobile apps and MCP agents. S256 only. Supports Dynamic Client Registration (RFC 7591), Client ID Metadata Document (CIMD) for URL-based client_id, and Resource Indicators (RFC 8707). See [OAuth reference](https://api.fast.io/current/llms/oauth/). - **API Keys:** Long-lived tokens. Same Bearer header. Create via `POST /current/user/auth/key/`. Keys optionally support scoped permissions (`scopes`), agent names (`agent_name`), and expiration (`expires`). Update via `POST /current/user/auth/key/{id}/`. - **2FA:** Limited-scope token → full token after `POST /current/user/auth/2factor/auth/{token}/`. **Choose your access pattern:** 1. **Human's account** — Human creates API key, gives it to you. You operate as them. → [Auth reference](https://api.fast.io/current/llms/auth/) 2. **Autonomous agent** — Create agent account (`agent=true`), create org, work independently. → [Auth reference](https://api.fast.io/current/llms/auth/) 3. **Collaboration** — Create agent account, human invites you to their org/workspace. → [Auth reference](https://api.fast.io/current/llms/auth/) 4. **PKCE browser login** — Secure, no password sharing, supports SSO. → [OAuth reference](https://api.fast.io/current/llms/oauth/) ## Response Envelope Success (data fields at root level): ```json {"result": true, "status": "ok", ...} ``` Error: ```json {"result": false, "error": {"code": 195654, "text": "Human-readable message", "documentation_url": "https://api.fast.io/llms.txt", "resource": "POST /current/user/"}} ``` - `result`: boolean — `true` on success, `false` on error - `error.code`: Unique error identifier (integer) for debugging - `error.text`: Human-readable error message, safe to display - `error.documentation_url`: Link to error documentation (string or null) - `error.resource`: The endpoint that produced the error ## Error Codes Client errors: - 1605 (Invalid Input) → 400 Bad Request - 1607 (Duplicate Entry) → 400 Bad Request - 1650 (Authentication Invalid) → 401 Unauthorized - 1651 (Invalid Request Type) → 405 Method Not Allowed - 1609 (Not Found) → 404 Not Found - 1652 (Resource Not Found) → 404 Not Found - 1653 (User Not Found) → 404 Not Found - 1656 (Limit Exceeded) → 400 Bad Request - 1667 (Max Limit) → 429 Too Many Requests - 1685 (Feature Limit) → 412 Precondition Failed - 1658 (Not Acceptable) → 406 Not Acceptable - 1660 (Conflict) → 409 Conflict - 1669 (Already Exists) → 409 Conflict - 1670 (Restricted) → 403 Forbidden - 1680 (Access Denied) → 403 Forbidden - 1671 (Rate Limited) → 429 Too Many Requests - 1677 (Locked) → 423 Locked Billing errors: - 1688 (Subscription Required) → 402 Payment Required (org has no active subscription or free-tier credits exhausted) - 1695 (Upgrade Required) → 402 Payment Required (feature requires a higher-tier plan) - 1696 (Credit Limit Exceeded) → 402 Payment Required (free-tier credit limit exceeded; error message includes usage details) Server errors: - 1654 (Internal Error) → 500 - 1664 (Datastore Error) → 500 - 1686 (Not Implemented) → 501 ## Rate Limiting Headers: `X-Rate-Limit-Available`, `X-Rate-Limit-Expiry`, `X-Rate-Limit-Max` When exceeded: HTTP 429 with error code 1671 (Rate Limited). ## Pagination ### Offset Pagination (List Endpoints) Most list endpoints support offset-based pagination: - `limit`: 1-500 (default: 100) — number of items to return - `offset`: 0+ (default: 0) — number of items to skip - Response: `pagination.total`, `pagination.limit`, `pagination.offset`, `pagination.has_more` ### Keyset Pagination (Storage List Endpoints) Storage listing uses cursor-based pagination: - `sort_by`: name | updated | created | type (default: name) - `sort_dir`: asc | desc (default: asc) - `page_size`: 100 | 250 | 500 (default: 100) - `cursor`: opaque string from previous response - Response: `pagination.has_more`, `pagination.next_cursor`, `pagination.page_size` ## ID Formats - **Profile IDs** (user, org, workspace, share): 19-digit numeric string - **Node IDs** (files, folders): OpaqueId — 30-character alphanumeric with hyphens (e.g., `f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4`). Prefix: `f` = file, `d` = folder, `n` = note. - **Upload IDs**: OpaqueId — alphanumeric string - **Opaque IDs** (quickshare tokens): 30-character alphanumeric string - **Special folder aliases**: `"root"` for storage root, `"trash"` for trash folder **Custom names as identifiers:** Most endpoints that accept a profile ID also accept a custom name: | Profile Type | Custom Name | |-------------|-------------| | Workspace | Folder name (e.g., `my-project`) | | Share | URL name (e.g., `q4-financials`) | | Organization | Domain name (e.g., `acme`) | | User | Email address (e.g., `user@example.com`) | ## Field Constraints Profile fields (org, workspace, share) have validation rules enforced server-side. | Entity | Field | API Key | Min | Max | Regex | Required | Nullable | |--------|-------|---------|-----|-----|-------|----------|----------| | Org | domain | `domain` | 2 | 80 | `^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$` | Yes (create) | No | | Org | name | `name` | 3 | 100 | No control chars (`\p{C}`) | Yes | No | | Org | description | `description` | 10 | 1000 | No control chars (`\p{C}`) | No | Yes | | Workspace | folder_name | `folder_name` | 4 | 80 | `^[\p{L}\p{N}-]+$` (unicode letters, digits, hyphens) | Yes (create) | No | | Workspace | name | `name` | 2 | 100 | No control chars (`\p{C}`) | Yes | No | | Workspace | description | `description` | 10 | 1000 | No control chars (`\p{C}`) | No | Yes | | Share | custom_name | `custom_name` | 4 | 80 | `^[\p{L}\p{N}]+$` (unicode letters and digits only) | Yes (create) | No | | Share | custom_url | `custom_url` | 10 | 100 | — | Yes | Yes | | Share | title | `title` | 2 | 80 | No control chars (`\p{C}`) | Yes | Yes | | Share | description | `description` | 10 | 500 | No control chars (`\p{C}`) | No | Yes | **Notes:** - "No control chars" means the field rejects any Unicode control characters (category `\p{C}`) - Org domain must be lowercase alphanumeric with optional hyphens, cannot start/end with hyphen - Workspace folder_name allows Unicode letters, digits, and hyphens - Share custom_name allows Unicode letters and digits only (no hyphens) - Description max length for shares is 500, not 1000 like org/workspace ## Agent Workflows ### Upload Files Small files (< 4MB): single-request upload. Large files: chunked upload with parallel chunks. → Full details: [Upload reference](https://api.fast.io/current/llms/upload/) ### Query Documents with AI Create chats with `chat` (general) or `chat_with_files` (RAG with citations). Stream responses via SSE. Use `/storage/search` for both keyword and semantic search — when workspace intelligence is enabled, results automatically include ranked text snippets with relevance scores. Add `details=true` to include the full node resource (previews, AI state, metadata, size) for each result; default limit drops to 10 when details enabled. Search is better for retrieval/lookup; chat is better for synthesis/analysis. → Full details: [AI reference](https://api.fast.io/current/llms/ai/) ### Share Files with Humans Create Send/Receive/Exchange shares with Portal or Shared Folder storage modes. → Full details: [Shares reference](https://api.fast.io/current/llms/shares/) ### Transfer Ownership to a Human Agent creates transfer token → build claim URL `https://go.fast.io/claim?token={token}` → human claims org. → Full details: [Organizations reference](https://api.fast.io/current/llms/orgs/) ### Monitor Usage `GET /current/org/{org_id}/billing/usage/limits/credits/` — credit consumption `GET /current/org/{org_id}/billing/usage/meters/list/` — detailed breakdown `GET /current/events/search/` — activity feed → Full details: [Events reference](https://api.fast.io/current/llms/events/) ### Activity Polling Long-poll for changes instead of looping on individual endpoints: `GET /current/activity/poll/{entityId}?wait=95&lastactivity={timestamp}` → Full details: [Events reference](https://api.fast.io/current/llms/events/) ## Endpoint Summary ### System - `GET /current/ping/` — Health check (no auth) - `GET /current/system/status/` — System status (no auth) - `GET /current/llms/` — This reference file (no auth) - `GET /current/agents/` — Agent integration guide (no auth) - `GET /.well-known/oauth-authorization-server/` — OAuth authorization server metadata (RFC 8414, no auth) - `GET /.well-known/oauth-protected-resource/` — OAuth protected resource metadata (RFC 9728, no auth) ### Users & Auth → [Auth reference](https://api.fast.io/current/llms/auth/) User CRUD, authentication (Basic Auth, API keys, 2FA), email validation, password reset, user search. - `GET /current/auth/scopes/` — Token scope introspection. Returns auth_type (jwt_v2, jwt_v1, api_key, api_key_scoped), scopes array, scopes_detail (hydrated with entity names/domains/parents), is_agent, agent_name, full_access. For scoped API keys, returns `auth_type: "api_key_scoped"` with hydrated scope details, agent name, and expiration. Requires auth. - `GET /current/user/auth/` — Basic Auth → JWT exchange - `POST /current/user/` — Create user account - `POST /current/user/update/` — Update user profile - `GET /current/user/{user_id}/details/` — User details - `POST /current/user/auth/key/` — Create API key - `GET /current/user/auth/keys/` — List API keys - `POST /current/user/auth/2factor/auth/` — 2FA verification - `POST /current/user/email/validate/` — Email verification - `POST /current/user/email/reset/` — Password reset request - `GET /current/users/search/` — Search users ### OAuth 2.0 → [OAuth reference](https://api.fast.io/current/llms/oauth/) PKCE authorization flow, token exchange/refresh, session management, Dynamic Client Registration (RFC 7591/7592), Client ID Metadata Document (CIMD), Resource Indicators (RFC 8707), scoped access tokens (JWT v2.0). - `GET /.well-known/oauth-authorization-server/` — Authorization server metadata (RFC 8414, no auth) - `GET /.well-known/oauth-protected-resource/` — Protected resource metadata (RFC 9728, no auth) - `POST /current/oauth/register/` — Dynamic client registration (no auth, rate limited) - `PUT /current/oauth/register/` — Update client registration (localhost-only clients) - `POST /current/oauth/token/` — Token exchange (authorization_code, refresh_token) - `POST /current/oauth/revoke/` — Revoke token - `GET /current/oauth/sessions/` — List OAuth sessions - `GET /current/oauth/authorize/info/` — Authorization request info ### Organizations → [Organizations reference](https://api.fast.io/current/llms/orgs/) Org CRUD, member management, billing/subscriptions, agent→human transfer, org discovery. - `POST /current/org/create/` — Create organization - `GET /current/org/{org_id}/details/` — Org details - `POST /current/org/{org_id}/update/` — Update org - `GET /current/org/{org_id}/members/list/` — List members - `POST /current/org/{org_id}/create/workspace/` — Create workspace - `GET /current/org/{org_id}/billing/details/` — Billing info - `GET /current/org/{org_id}/billing/invoices/` — List invoices with hosted payment links - `POST /current/org/{org_id}/transfer/token/create/` — Create transfer token ### Workspaces → [Workspaces reference](https://api.fast.io/current/llms/workspaces/) Workspace CRUD, member management, assets, intelligence setting, discovery. - `GET /current/workspace/{workspace_id}/details/` — Workspace details - `POST /current/workspace/{workspace_id}/update/` — Update workspace - `GET /current/workspace/{workspace_id}/members/list/` — List members - `POST /current/workspace/{workspace_id}/create/share/` — Create share - `GET /current/workspace/{workspace_id}/list/shares/` — List shares ### Storage → [Storage reference](https://api.fast.io/current/llms/storage/) File/folder CRUD (both workspace and share), locking, previews, transforms, download tokens, recently modified files. - `GET /current/workspace/{id}/storage/{parent}/list/` — List folder contents - `GET /current/workspace/{id}/storage/{node}/details/` — Node details - `GET /current/workspace/{id}/storage/{node}/read/` — Download file - `POST /current/workspace/{id}/storage/{parent}/addfile/` — Add uploaded file - `POST /current/workspace/{id}/storage/{parent}/createfolder/` — Create folder - `POST /current/workspace/{id}/storage/{node}/lock/` — Acquire file lock - `POST /current/workspace/{id}/storage/{node}/lock/heartbeat/` — Renew lock - `DELETE /current/workspace/{id}/storage/{node}/lock/` — Release lock - `GET /current/workspace/{id}/storage/search/` — Search files ### Shares → [Shares reference](https://api.fast.io/current/llms/shares/) Share CRUD, types (Send/Receive/Exchange), storage modes (Portal/Shared Folder), members, branding, quickshare, anonymous file drop (guest auth). Shares support `download_security` levels: `off` (default), `medium` (preview-only for guests via nonce flow), `high` (downloads disabled for guests). - `POST /current/workspace/{id}/create/share/` — Create share - `GET /current/share/{share_id}/details/` — Share details - `POST /current/share/{share_id}/update/` — Update share - `GET /current/share/{share_id}/members/list/` — List members - `POST /current/share/{share_id}/auth/guest/` — Guest authentication - `POST /current/share/{share_id}/auth/password/` — Password authentication ### Upload → [Upload reference](https://api.fast.io/current/llms/upload/) Chunked upload flow, web upload (URL import), session management. - `POST /current/upload/` — Create upload session - `POST /current/upload/{id}/chunk/` — Upload chunk - `POST /current/upload/{id}/stream/` — Stream entire file (unknown size) - `POST /current/upload/{id}/complete/` — Finalize upload - `GET /current/upload/{id}/details/` — Check upload status - `POST /current/web_upload/` — Import file from URL ### AI & Chat → [AI reference](https://api.fast.io/current/llms/ai/) RAG chat, general chat, semantic search, notes, metadata templates with many-to-many file mappings, AI extraction, SSE streaming. - `POST /current/workspace/{id}/ai/chat/` — Create AI chat - `POST /current/workspace/{id}/ai/chat/{chat_id}/message/` — Send message - `GET /current/workspace/{id}/ai/chat/{chat_id}/message/{msg_id}/read/` — Stream response (SSE) - `GET /current/workspace/{id}/ai/search/` — Semantic search (**deprecated** — use `/storage/search` instead) - `GET /current/workspace/{id}/metadata/eligible/` — List files eligible for metadata extraction - `POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/add/` — Add files to template - `POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/remove/` — Remove files from template - `GET /current/workspace/{id}/metadata/templates/{template_id}/nodes/` — List files in template - `POST /current/workspace/{id}/metadata/templates/{template_id}/auto-match/` — AI-based file matching - `POST /current/workspace/{id}/metadata/templates/{template_id}/extract-all/` — Batch extract metadata - `POST /current/workspace/{id}/storage/{node}/metadata/extract/` — Extract metadata (accepts template_id) ### Events & Activity → [Events reference](https://api.fast.io/current/llms/events/) Event search/filtering, activity polling, WebSocket realtime. - `GET /current/events/search/` — Search events - `GET /current/events/search/summarize/` — AI event summary - `GET /current/event/{event_id}/details/` — Event details - `POST /current/event/{event_id}/ack/` — Acknowledge event - `GET /current/activity/poll/{profile_id}/` — Long-poll for changes - `GET /current/realtime/auth/{room_id}` — Realtime room auth - `GET /current/realtime/auth/validate/` — Validate realtime token - `GET /current/websocket/auth/{profile_id}` — WebSocket auth ### Comments → [Comments reference](https://api.fast.io/current/llms/comments/) Comment CRUD (JSON body), threading, mentions, reactions, reference anchoring. - `POST /current/comments/{entity_type}/{parent_id}/` — Create comment - `GET /current/comments/{entity_type}/{parent_id}/` — List comments - `GET /current/comments/{comment_id}/details/` — Comment details - `POST /current/comments/{comment_id}/reactions/` — Add reaction - `DELETE /current/comments/{comment_id}/delete/` — Delete comment ### Workflow (Task Management) → [Workflow reference](https://api.fast.io/current/llms/workflow/) Workflow features enable structured task management, logging, and approval processes within workspaces and shares. All workflow endpoints require the `workflow` feature to be enabled on the target workspace or share. On shares, workflow access (tasks, todos, approvals, worklogs) requires Member-level access or higher — guests and public guests are denied. Toggle endpoints (enable/disable) remain Admin-only. **Task Lists & Tasks:** - `GET /current/workspace/{workspace_id}/tasks/` - List task lists - `GET /current/share/{share_id}/tasks/` - List task lists - `POST /current/workspace/{workspace_id}/tasks/create/` - Create task list - `POST /current/share/{share_id}/tasks/create/` - Create task list - `GET /current/tasks/{list_id}/details/` - Task list details - `POST /current/tasks/{list_id}/update/` - Update task list - `POST /current/tasks/{list_id}/delete/` - Delete task list - `GET /current/tasks/{list_id}/items/` - List tasks - `POST /current/tasks/{list_id}/items/create/` - Create task - `GET /current/tasks/{list_id}/items/{task_id}/` - Task details - `POST /current/tasks/{list_id}/items/{task_id}/update/` - Update task - `POST /current/tasks/{list_id}/items/{task_id}/delete/` - Delete task - `POST /current/tasks/{list_id}/items/{task_id}/status/` - Change status - `POST /current/tasks/{list_id}/items/{task_id}/assign/` - Assign task - `POST /current/tasks/{list_id}/items/{task_id}/move/` - Move task to another list - `POST /current/tasks/{list_id}/items/bulk-status/` - Bulk status change - `POST /current/tasks/{list_id}/items/reorder/` - Bulk reorder tasks - `POST /current/workspace/{workspace_id}/tasks/reorder/` - Bulk reorder task lists - `POST /current/share/{share_id}/tasks/reorder/` - Bulk reorder task lists **Worklogs:** - `GET /current/worklogs/{entity_type}/{entity_id}/` - List entries - `POST /current/worklogs/{entity_type}/{entity_id}/append/` - Append entry - `POST /current/worklogs/{entity_type}/{entity_id}/interjection/` - Create interjection - `GET /current/worklogs/{entity_type}/{entity_id}/interjections/` - List unacknowledged - `GET /current/worklogs/{entry_id}/details/` - Entry details - `POST /current/worklogs/{entry_id}/acknowledge/` - Acknowledge interjection **Approvals:** - `GET /current/workspace/{workspace_id}/approvals/` - List approvals - `GET /current/share/{share_id}/approvals/` - List approvals - `POST /current/approvals/{entity_type}/{entity_id}/create/` - Create approval - `GET /current/approvals/{approval_id}/details/` - Approval details - `POST /current/approvals/{approval_id}/resolve/` - Resolve approval **Todos:** - `GET /current/workspace/{workspace_id}/todos/` - List todos - `GET /current/share/{share_id}/todos/` - List todos - `POST /current/workspace/{workspace_id}/todos/create/` - Create todo - `POST /current/share/{share_id}/todos/create/` - Create todo - `GET /current/todos/{todo_id}/details/` - Todo details - `POST /current/todos/{todo_id}/details/update/` - Update todo - `POST /current/todos/{todo_id}/details/delete/` - Delete todo - `POST /current/todos/{todo_id}/details/toggle/` - Toggle done - `POST /current/workspace/{workspace_id}/todos/bulk-toggle/` - Bulk toggle - `POST /current/share/{share_id}/todos/bulk-toggle/` - Bulk toggle **Workflow Toggle:** - `POST /current/workspace/{workspace_id}/workflow/enable/` - Enable workflow - `POST /current/workspace/{workspace_id}/workflow/disable/` - Disable workflow - `POST /current/share/{share_id}/workflow/enable/` - Enable workflow - `POST /current/share/{share_id}/workflow/disable/` - Disable workflow Workflow and import GET endpoints support `format=md` for Markdown output. All list endpoints support `limit` and `offset` pagination. All workflow endpoints (except toggle) require the `workflow` feature enabled on the target profile. Share workflow access requires Member-level or higher. ### Cloud Import (External Storage Sync) Cloud Import connects external cloud storage providers to workspace storage. Sync files from Google Drive, Dropbox, Box, or OneDrive for Business. Requires `cloud_import` feature enabled on the workspace. **Feature Toggle:** - `POST /current/workspace/{workspace_id}/cloud-import/enable/` - Enable cloud import (admin) - `POST /current/workspace/{workspace_id}/cloud-import/disable/` - Disable cloud import (admin) **Available Providers:** - `GET /current/imports/workspace/{workspace_id}/providers/` - List providers available on workspace's plan **Provider Identities:** - `GET /current/imports/workspace/{workspace_id}/identities/` - List provider identities - `POST /current/imports/workspace/{workspace_id}/identities/provision/` - Provision identity (admin) - `GET /current/imports/workspace/{workspace_id}/identities/{identity_id}/` - Identity details - `POST /current/imports/workspace/{workspace_id}/identities/{identity_id}/revoke/` - Revoke identity (admin) **Import Sources:** - `GET /current/imports/workspace/{workspace_id}/sources/` - List import sources - `POST /current/imports/workspace/{workspace_id}/sources/discover/` - Discover remote folders (admin) - `POST /current/imports/workspace/{workspace_id}/sources/create/` - Create import source (admin) **Source Details:** - `GET /current/imports/details/{source_id}/` - Source details - `POST /current/imports/details/{source_id}/update/` - Update settings (admin) - `POST /current/imports/details/{source_id}/delete/` - Soft-delete source (admin) - `POST /current/imports/details/{source_id}/disconnect/` - Disconnect (keep/delete files, admin) - `POST /current/imports/details/{source_id}/refresh/` - Trigger immediate sync **Import Jobs:** - `GET /current/imports/details/{source_id}/jobs/` - List sync jobs - `GET /current/imports/details/{source_id}/jobs/{job_id}/` - Job details with progress - `POST /current/imports/details/{source_id}/jobs/{job_id}/cancel/` - Cancel job (admin) **Write-back (Read-Write Sources):** - `GET /current/imports/details/{source_id}/writebacks/` - List write-back jobs - `POST /current/imports/details/{source_id}/writebacks/push/{node_id}/` - Force push file (admin) - `GET /current/imports/details/{source_id}/writebacks/{writeback_id}/` - Write-back details - `POST /current/imports/details/{source_id}/writebacks/{writeback_id}/retry/` - Retry failed (admin) - `POST /current/imports/details/{source_id}/writebacks/{writeback_id}/resolve/` - Resolve conflict (admin) - `POST /current/imports/details/{source_id}/writebacks/{writeback_id}/cancel/` - Cancel write-back (admin) Providers: `google_drive`, `dropbox`, `box`, `onedrive_business`. Source statuses: pending, discovering, syncing, synced, error, paused, disconnected, disconnecting. Sources with >1000 files use async disconnect. Import GET endpoints support `format=md`. ## Common Patterns - List endpoints return arrays in a `response` wrapper with consistent pagination - All profile operations require membership with sufficient permissions - Owner > Admin > Member > Guest permission hierarchy - `"me"` can be used as user_id to reference the authenticated user - Profile path parameters accept either a 19-digit numeric ID or a custom name - Storage operations (workspace and share) follow identical patterns - AI chat endpoints (workspace and share) follow identical patterns - Member management endpoints (org, workspace, share) follow identical patterns - Long-polling supported on activity/poll and upload/details endpoints - Most POST endpoints use `application/x-www-form-urlencoded` bodies; comments use `application/json` ## Additional Resources - Full single-file reference: https://api.fast.io/current/llms/full/ - Agent integration guide: available at `/current/agents/` - MCP Server: `https://mcp.fast.io/mcp` (Streamable HTTP) or `https://mcp.fast.io/sse` (legacy SSE) - MCP Skills: available at `/skill.md` on the MCP server - Claim URL for org transfer: `https://go.fast.io/claim?token={token}` > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Authentication & User Management Base URL: `https://api.fast.io/current/` Request format: `application/x-www-form-urlencoded` (POST bodies) or query string (GET) Response format: JSON --- ## Authentication Methods All authenticated endpoints require: `Authorization: Bearer {token}` The token can be a JWT (from Basic Auth or OAuth), an API key, or a 2FA-upgraded JWT. ### Method 1: Basic Auth to JWT Send HTTP Basic Auth (`email:password`) to get a JWT. ``` GET /current/user/auth/ Authorization: Basic {base64(email:password)} ``` Returns `auth_token` (JWT). If the account has 2FA enabled, the returned token has limited scope until 2FA verification is completed. ### Method 2: API Keys Long-lived tokens for service-to-service communication. Created via the API or the web UI. Used with the same `Authorization: Bearer {api_key}` header format as JWTs. Keys optionally support scoped permissions (`scopes`), agent names (`agent_name`), and expiration (`expires`). Scoped keys are enforced using the same scope system as v2.0 JWT tokens. Legacy keys without scopes retain full access. **Scope formats:** OAuth authorization requests accept named scope strings (`user`, `org`, `workspace`, `all_orgs`, `all_workspaces`, `all_shares`). API responses return scopes as arrays of `entity_type:entity_id:access_mode` strings (e.g., `["org:12345:rw"]`). See the [OAuth 2.0 reference](https://api.fast.io/current/llms/oauth/) for full scope format details. ### Method 3: OAuth 2.0 PKCE For desktop/mobile apps and MCP-connected agents. No password passes through the agent. Access tokens last 1 hour, refresh tokens 30 days. S256 challenge method only. See the [OAuth 2.0 reference](https://api.fast.io/current/llms/oauth/) for the full flow. ### Method 4: 2FA When 2FA is enabled on an account, Basic Auth returns a limited-scope JWT. Complete authentication via `POST /current/user/auth/2factor/auth/{token}/` with the 2FA code. The response contains a full-scope JWT. --- ## Getting Started ### Option 1: Use a Human's Existing Account (API Key) A human creates an API key and gives it to you. You operate as that user with their permissions, org, and billing. **Human instructions:** "Go to Settings > Devices & Agents > API Keys and click Create API Key. Optionally enter a memo to label the key (e.g., 'Agent access'), then click Create. Copy the key immediately -- it is only displayed once. Direct link: https://go.fast.io/settings/api-keys" Once you have the key: `Authorization: Bearer {api_key}`. No further steps needed. ### Option 2: Create Your Own Agent Account (Autonomous) Create your own account to work independently. 1. `POST /current/user/` with `email_address`, `password`, `tos_agree=true`, `agent=true` 2. `GET /current/user/auth/` with Basic Auth to get JWT 3. Verify email: - `POST /current/user/email/validate/` with `email` -- sends verification code - `POST /current/user/email/validate/` with `email` and `email_token` -- validates the code 4. `POST /current/org/create/` with `domain` (required, 2-80 chars lowercase alphanumeric + hyphens) 5. `POST /current/org/{org_id}/create/workspace/` with `folder_name`, `name`, `perm_join`, `perm_member_manage` Agent accounts get the agent plan: free, 50 GB storage, 5,000 credits/month, no expiration. ### Option 3: Agent Account Invited to a Human's Org 1. Create an agent account (steps 1-2 from Option 2) 2. Give the human your agent's email address 3. Human invites agent to their org or workspace 4. Accept: `POST /current/org/{org_id}/members/join/` or `POST /current/workspace/{workspace_id}/members/join/` 5. You now operate within their resources with granted permissions ### Option 4: PKCE Browser Login (No Password Sharing) Most secure option. Works with SSO. No credentials pass through the agent. 1. Agent initiates PKCE flow via `POST /current/oauth/authorize/` with `code_challenge`, `code_challenge_method=S256`, `client_id`, `redirect_uri`, `response_type=code` 2. User opens the returned URL in browser, signs in, approves access 3. Browser displays authorization code -- user copies it to agent 4. Agent calls `POST /current/oauth/token/` with `grant_type=authorization_code`, `code`, `code_verifier` 5. Access tokens last 1 hour; refresh via `POST /current/oauth/token/` with `grant_type=refresh_token` **Which option to choose:** - Human wants you to manage *their* account -> Option 1 (API key) - You're building something independently -> Option 2 (agent account + own org) - You need to work within a human's existing org -> Option 3 (agent account + invitation) - Human wants to authorize agent without sharing credentials -> Option 4 (PKCE) --- ## User Creation ### POST /current/user/ Create a new user account. **Auth:** None (IP-throttled) **Request Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `email_address` | string | Yes | Valid email format; domain must accept email; must be unique | User's email address. Tags (e.g., `+tag`) are stripped for storage and uniqueness checks, but the original is preserved. | | `password` | string | Yes | Must pass password validity checks | Account password. | | `tos_agree` | string | Yes | Must be `"true"` | Must be `"true"` to accept Terms of Service. | | `agent` | string | No | `"true"` or `"false"` | Set `"true"` for AI agent accounts. Sets `account_type` to `"agent"` permanently. Agent accounts skip email validation and use the agent plan. | | `first_name` | string | No | Must pass name validation | User's given/first name. | | `last_name` | string | No | Must pass name validation | User's family/last name. | | `phone_country` | string | No | Numeric country calling code | Phone country code. Required if `phone_number` is provided. | | `phone_number` | string | No | Numeric phone number | Phone number. Required if `phone_country` is provided. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/" \ -d "email_address=jane.doe@example.com" \ -d "password=SecureP@ss123" \ -d "tos_agree=true" \ -d "first_name=Jane" \ -d "last_name=Doe" \ -d "agent=true" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `current_api_version` | string | API version (`"1.0"`) | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid email was supplied." | Email format invalid | | `1605 (Invalid Input)` | 400 | "The email domain is invalid or cannot receive email." | Email domain validation failed | | `1605 (Invalid Input)` | 400 | "The email supplied is already in use." | Email already registered (after normalization) | | `1605 (Invalid Input)` | 400 | "An invalid password was supplied." | Password does not meet requirements | | `1605 (Invalid Input)` | 400 | "An invalid `tos_agree` value was create." | TOS value not a valid boolean string | | `1605 (Invalid Input)` | 400 | "You declined to accept the terms of service." | TOS set to `"false"` | | `1605 (Invalid Input)` | 400 | "An invalid first name was supplied to create." | First name fails validation | | `1605 (Invalid Input)` | 400 | "An invalid last name was supplied to create." | Last name fails validation | | `1605 (Invalid Input)` | 400 | "An invalid phone country code was supplied." | Invalid phone country code | | `1605 (Invalid Input)` | 400 | "An invalid phone number was supplied." | Invalid phone number | | `1605 (Invalid Input)` | 400 | "An invalid phone number or country code was supplied." | Full phone number validation failed | | `1680 (Access Denied)` | 403 | "Your attempt to create an account was not accepted." | Risk/fraud check failed | | `1654 (Internal Error)` | 500 | "We were unable to create your user account..." | Internal processing failure | **Notes:** - Email addresses are normalized by stripping tag extensions (e.g., `user+tag@example.com` becomes `user@example.com`) for storage and uniqueness lookup; the original email is preserved separately. - Country code is detected from the client IP and stored automatically. - `agent=true` is permanent and cannot be changed after account creation. - Agent accounts skip email validation requirements and can authenticate immediately. --- ## User Management Endpoints ### POST /current/user/update/ Update the current authenticated user's profile information. **Auth:** Required (JWT) **Request Parameters:** All fields are optional. Only provided fields are updated. | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `email_address` | string | No | Valid email format; unique; domain must accept email | New email address. Resets email verification status. | | `password` | string | No | Must pass validity checks | New password. | | `first_name` | string | No | Must pass name validation | Updated given/first name. | | `last_name` | string | No | Must pass name validation | Updated family/last name. | | `phone_country` | string | No | Numeric country code; 2FA must be disabled first | Updated phone country code. Pass `"null"` or empty to clear. | | `phone_number` | string | No | Numeric phone number; 2FA must be disabled first | Updated phone number. Pass `"null"` or empty to clear. | | `owner_defined` | string (JSON) | No | Must be valid JSON if provided | Custom owner-defined properties. Pass `null` or empty to clear. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "first_name=Jane" \ -d "last_name=Smith" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid email was supplied to update." | Invalid email format | | `1605 (Invalid Input)` | 400 | "The email domain is invalid or cannot receive email." | Invalid email domain | | `1660 (Conflict)` | 409 | "There email you specified is not available." | Email already in use | | `1670 (Restricted)` | 403 | "You must disable 2-Factor before updating your phone." | 2FA enabled when trying to change phone | | `1605 (Invalid Input)` | 400 | "An invalid password was supplied to update." | Invalid password | | `1605 (Invalid Input)` | 400 | "An invalid first name was supplied to update." | Invalid first name | | `1605 (Invalid Input)` | 400 | "An invalid last name was supplied to update." | Invalid last name | | `1605 (Invalid Input)` | 400 | "Owner-defined properties must be valid JSON." | Invalid JSON in `owner_defined` | **Notes:** - Changing the email address resets email verification status. - Phone number changes require 2FA to be disabled first. - If no fields have changed, the endpoint returns success without making changes. --- ### POST /current/user/close/ Close (soft-delete) the current user's account. **Auth:** Required (JWT) **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `email_address` | string | Yes | Must match the user's current email address (confirmation). | | `dryrun` | string | No | If truthy, checks eligibility without closing the account. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/close/" \ -H "Authorization: Bearer {jwt_token}" \ -d "email_address=jane.doe@example.com" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Dry Run Response (Cannot Close, 202 Accepted):** ```json { "result": "no", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1653 (User Not Found)` | 404 | "User not found to close." | User object invalid | | `1605 (Invalid Input)` | 400 | "An invalid email was supplied to close account." | Invalid email format | | `1605 (Invalid Input)` | 400 | "An incorrect email was supplied to close account." | Email does not match user's email | | `1605 (Invalid Input)` | 400 | "Cannot close user account that owns active organizations..." | User owns active organizations | **Notes:** - 2FA verification is required if 2FA is enabled on the account. - Users who own active organizations must close or transfer ownership first. - The `dryrun` parameter checks closure eligibility without actually closing the account. - On closure: subscriptions are cancelled, SSO connections are removed, the account is flagged as closed. --- ### POST /current/user/email/ Check if an email address is already in use. **Auth:** None (IP-throttled) **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `email` | string | Yes | Email address to check availability. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/email/" \ -d "email=jane.doe@example.com" ``` **Email Available (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Email In Use (406 Not Acceptable):** ```json { "result": "no", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid email to check." | Invalid email format or missing | **Notes:** - The email is normalized (tags stripped) before lookup. - `result: "yes"` = available, `result: "no"` = already in use. --- ### POST /current/user/email/reset/ Request a password reset email. **Auth:** None (IP-throttled) **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `email` | string | Yes | Email address of the account. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/email/reset/" \ -d "email=jane.doe@example.com" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid email to check." | Invalid email format | | `1654 (Internal Error)` | 500 | "We were unable to send a verification email." | Email send failure | **Notes:** - For security, this endpoint always returns success regardless of whether the email exists in the system. --- ### POST /current/user/email/validate/ Send or validate an email verification code. Two-step flow. **Auth:** Required (JWT) **Mode 1: Send Verification Code** When `email_token` is NOT provided, sends a new validation code to the user's email. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `email` | string | Yes | Must match the authenticated user's email address. | **Mode 2: Validate Code** When `email_token` IS provided, validates the code and marks the email as verified. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `email` | string | Yes | Must match the authenticated user's email address. | | `email_token` | string | Yes | Verification code received via email. | **Request Example (Send Code):** ```bash curl -X POST "https://api.fast.io/current/user/email/validate/" \ -H "Authorization: Bearer {jwt_token}" \ -d "email=jane.doe@example.com" ``` **Request Example (Validate Code):** ```bash curl -X POST "https://api.fast.io/current/user/email/validate/" \ -H "Authorization: Bearer {jwt_token}" \ -d "email=jane.doe@example.com" \ -d "email_token=123456" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Your credentials were not supplied or invalid." | User not authenticated | | `1658 (Not Acceptable)` | 406 | "Your email address is already verified." | Email already verified | | `1660 (Conflict)` | 409 | "Your credentials do not match the email you provided." | Email mismatch with authenticated user | | `1658 (Not Acceptable)` | 406 | "You provided an invalid or expired token to validate email." | Invalid or expired code | | `1680 (Access Denied)` | 403 | "Provided code has expired, get a new code and try again." | Code expired | --- ### POST /current/user/password/{code}/ Set a new password using a password reset code. **Auth:** None (code-based authentication) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{code}` | string | Yes | Password reset code from the reset email. | **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `password1` | string | Yes | New password. | | `password2` | string | Yes | New password confirmation. Must match `password1`. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/password/abc123def456/" \ -d "password1=NewSecureP@ss" \ -d "password2=NewSecureP@ss" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "An invalid code was provided, cannot reset password." | Invalid code format | | `1680 (Access Denied)` | 403 | "Provided code was not found or expired, cannot reset password." | Code not found or wrong type | | `1680 (Access Denied)` | 403 | "Provided code has expired, get a new code and try again." | Code expired | | `1660 (Conflict)` | 409 | "Provided code belongs to another user account and cannot be used." | Code/user mismatch | | `1683 (Resource Missing)` | 404 | "The provided code belongs to an invalid user." | User not found for code | | `1660 (Conflict)` | 409 | "The provided passwords don't match." | `password1` and `password2` differ | | `1658 (Not Acceptable)` | 406 | "Both password fields must be provided and match." | Missing password fields | | `1654 (Internal Error)` | 500 | "The provided password could not be processed..." | Encryption failure | **Notes:** - The reset code is consumed (deleted) after successful password change. - A new JWT is created after the password is set. --- ### GET /current/user/password/{code}/details/ Get details of a password reset code (check if valid/expired). **Auth:** None (code-based) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{code}` | string | Yes | Password reset code to check. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/password/abc123def456/details/" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "email": "jane.doe@example.com" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.email` | string | The email address associated with the reset code. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "An invalid code was provided, cannot reset password." | Invalid code format | | `1680 (Access Denied)` | 403 | "Provided code was not found or expired, cannot reset password." | Code not found | | `1680 (Access Denied)` | 403 | "Provided code has expired, get a new code and try again." | Code expired | | `1660 (Conflict)` | 409 | "Provided code belongs to another user account..." | Code/user mismatch | | `1677 (Locked)` | 423 | "The account has been restricted and cannot be updated." | Account locked/suspended/closed | --- ### GET /current/user/phone/{country_code}-{phone_number}/ Validate a phone number and country code combination. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{country_code}-{phone_number}` | string | Yes | Country code and phone number separated by a hyphen (e.g., `1-5551234567`). | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/phone/1-5551234567/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid phone number to check." | Invalid format | | `1605 (Invalid Input)` | 400 | "An invalid phone country code was supplied." | Invalid country code | | `1605 (Invalid Input)` | 400 | "An invalid phone number was supplied." | Invalid phone number | | `1605 (Invalid Input)` | 400 | "An invalid phone number or country code was supplied." | Full number validation failed | --- ### GET /current/user/pin/ Get the user's support PIN and identity verification hash. **Auth:** Required (JWT) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/pin/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "supportcode": "1234", "support_hash": "a1b2c3d4e5f6..." }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.supportcode` | string | 4-digit support PIN. Defaults to `"0000"` if not set. | | `response.support_hash` | string | Identity verification hash for support integration. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1653 (User Not Found)` | 404 | "Unable to fetch the user details." | User not found | | `1654 (Internal Error)` | 500 | "Internal temporary error, please try again later." | Failed to load credentials | --- ### GET|POST /current/user/sso/signin/{provider}/ SSO (Single Sign-On) authentication flow. **Auth:** None (IP-throttled) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{provider}` | string | Yes | SSO provider name: `google`, `apple`, or `microsoft`. | **GET: Get SSO Redirect URL** Returns the OAuth2 authorization URL for the specified provider. ```bash curl -X GET "https://api.fast.io/current/user/sso/signin/google/" ``` **GET Response (200 OK):** ```json { "result": "yes", "response": { "provider": "google", "redirect_url": "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=...", "return_url": "https://fast.io/sso/callback/google" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.provider` | string | The provider name. | | `response.redirect_url` | string | URL to redirect the user to for SSO authentication. | | `response.return_url` | string | Callback URL the provider will redirect back to. | **POST: Process SSO Callback** Processes the OAuth2 callback with the authorization code from the provider. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `code` | string | Yes | Authorization code from the SSO provider. | | `state` | string | Yes | State token for CSRF protection. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid provider name was supplied." | Invalid provider name format | | `1605 (Invalid Input)` | 400 | "An unknown provider name was supplied." | Provider not in allowed list | | `1605 (Invalid Input)` | 400 | "Cookies must be enabled and passed to this API." | Missing state cookie | | `1680 (Access Denied)` | 403 | "Permission was not granted by the provider." | OAuth error returned from provider | | `1673 (SSO Auth Error)` | 401 | "Invalid or missing input in a required field was received." | Missing code or state | **Notes:** - Supported providers: `google`, `apple`, `microsoft`. - GET generates a state token (requires cookies) and returns the redirect URL. - POST exchanges the authorization code for tokens and creates/links the user account. --- ### GET /current/user/assets/ List available user asset metadata types (e.g., profile photo specifications). **Auth:** None **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/assets/" ``` **Notes:** - Returns the schema/specifications for available asset types, not actual assets. --- ### GET /current/user/available_profiles/ Check what profile types (orgs, workspaces, shares) the current user has access to. **Auth:** Required (JWT) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/available_profiles/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "has_orgs": true, "has_workspaces": true, "has_shares": false }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.has_orgs` | boolean | Whether the user has access to any organizations. | | `response.has_workspaces` | boolean | Whether the user has access to any workspaces. | | `response.has_shares` | boolean | Whether the user has access to any shares. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1653 (User Not Found)` | 404 | "Unable to fetch the user details." | User not found | --- ### GET /current/user/{user_id}/details/ Get user profile details. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{user_id}` | string | No | 19-digit user ID. If omitted, returns the current user's details. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/1234567890123456789/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "user": { "id": "12345678901234567890", "account_type": "human", "email_address": "jane.doe@example.com", "first_name": "Jane", "last_name": "Doe", "locked": false, "profile_pic": "https://assets.fast.io/..." } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.user.id` | string | 19-digit user ID. | | `response.user.account_type` | string | `"human"` or `"agent"`. | | `response.user.email_address` | string | User's email address. | | `response.user.first_name` | string | Given name. | | `response.user.last_name` | string | Family name. | | `response.user.locked` | boolean | Whether the account is locked. | | `response.user.profile_pic` | string | Profile photo URL. | **Self-Only Fields** (included only when viewing your own profile): | Field | Type | Description | |-------|------|-------------| | `2factor` | boolean | Whether 2FA is enabled. | | `closed` | boolean | Whether the account is closed. | | `country_code` | string | Country of residence. | | `created` | string | Registration date. | | `phone_country` | string | Phone country code. | | `phone_number` | string | Phone number. | | `suspended` | boolean | Suspension status. | | `tos_agree` | string | ToS agreement date. | | `updated` | string | Last profile update time. | | `valid_email` | boolean | Email verified status. | | `valid_phone` | boolean | Phone verified status. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1653 (User Not Found)` | 404 | "Unable to fetch the user details." | User not found | --- ### GET /current/user/me/autosync/{state}/ Enable or disable profile photo auto-synchronization from SSO providers. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{state}` | string | Yes | `"enable"` or `"disable"`. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/me/autosync/enable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1664 (Datastore Error)` | 500 | "There was an internal error processing your request..." | Internal processing failure | --- ### GET /current/user/me/allowed/ Check if the user's country (based on IP geolocation) allows creating shares or organizations. **Auth:** None (IP-throttled) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/me/allowed/" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "allowed": true }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.allowed` | boolean | Whether the user's location allows resource creation. | | `response.reasons` | array | Array of blocked reason strings. Only present when `allowed` is `false`. | --- ### GET /current/user/me/limits/orgs/ Check free organization creation eligibility. **Auth:** Required (JWT) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/me/limits/orgs/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "can_create_free_org": true, "existing_free_orgs": 0, "cooldown_remaining": 0, "max_free_orgs": 1 }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.can_create_free_org` | boolean | Whether the user can create a free organization. | | `response.existing_free_orgs` | integer | Number of existing free organizations owned by the user. | | `response.cooldown_remaining` | integer | Seconds remaining before next creation is allowed. | | `response.max_free_orgs` | integer | Maximum number of free organizations allowed. | | `response.reason` | string | Reason creation is not allowed. Only present when `can_create_free_org` is `false`. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1653 (User Not Found)` | 404 | "Unable to fetch the user." | User not found | --- ### GET /current/user/me/list/shares/ List all shares accessible to the current user. **Auth:** Required (JWT) **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `archived` | string | No | `"false"` | `"true"` to show archived shares, `"false"` to show non-archived. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/me/list/shares/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "shares": [ { "id": "12345678901234567890", "name": "Project Files", "type": "send", "archived": false } ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.shares` | array | Array of share resource objects. Each includes parent workspace and org info. | **Notes:** - Shares are gathered from three sources: owned by the user, invited to, and joined. - Duplicates are removed by share ID. - Does NOT include shares from workspaces the user has access to -- only shares with direct user relationships. --- ### GET /current/user/{user_id}/assets/ List set assets (e.g., profile photo) for a user. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{user_id}` | string | Yes | 19-digit numeric user ID. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/12345678901234567890/assets/" \ -H "Authorization: Bearer {jwt_token}" ``` --- ### POST|DELETE /current/user/{user_id}/assets/{asset_name}/ Upload or delete a user asset. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{user_id}` | string | Yes | 19-digit numeric user ID. | | `{asset_name}` | string | Yes | Asset type name (e.g., `profile_pic`). | **POST: Upload Asset** Multipart form data with exactly one file upload. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | (file) | file | Yes | The asset file. Exactly one file must be included. | | `metadata` | array | No | Optional metadata. Must be a valid array if provided. | **DELETE: Delete Asset** No request body required. **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1651 (Invalid Request Type)` | 400 | "Only user may modify." | Non-owner attempting to modify | | `1604 (File Missing)` | 400 | "Asset upload missing" | No file in POST request | | `1605 (Invalid Input)` | 400 | "metadata invalid" | Invalid metadata parameter | **Notes:** - Only the user themselves can modify their own assets. - Uploading or deleting disables profile photo auto-sync. --- ### GET|HEAD /current/user/{user_id}/assets/{asset_name}/read/ Read the binary content of a user asset. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{user_id}` | string | Yes | 19-digit numeric user ID. | | `{asset_name}` | string | Yes | Asset type name (e.g., `profile_pic`). | **Notes:** - Returns raw binary bytes with appropriate content-type headers, not JSON. - HEAD returns headers only. --- ## Invitations ### GET /current/user/invitation/{invitation_id}/details/ Get details for a specific invitation. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{invitation_id}` | string | Yes | Invitation ID (numeric) or invitation key (alphanumeric). | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/invitation/12345678901234567890/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "invitation": { "id": "12345678901234567890", "entity": "98765432101234567890", "state": "pending", "email": "jane.doe@example.com" }, "owner": { "id": "11111111111111111111", "account_type": "human", "email_address": "admin@example.com", "first_name": "Admin", "last_name": "User", "profile_pic": "https://assets.fast.io/..." }, "org": { "id": "22222222222222222222", "name": "Example Org" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.invitation` | object | Invitation resource. | | `response.owner` | object | User resource of the profile owner. | | `response.org` | object or null | Org resource if the invitation is for an org-owned entity. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation ID was supplied." | Invalid ID format | | `1605 (Invalid Input)` | 400 | "Invitation not found." | Invitation does not exist | | `1654 (Internal Error)` | 500 | "Failed to load the invitation profile or its owner." | Profile or owner load failure | --- ### GET /current/user/invitation/{invitation_id}/public/details/ Get public details for an invitation without authentication. **Auth:** None (IP-throttled) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{invitation_id}` | string | Yes | Invitation ID (numeric) or invitation key (alphanumeric). | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/invitation/12345678901234567890/public/details/" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "invitation": { "id": "12345678901234567890", "state": "pending" }, "owner": { "id": "11111111111111111111", "account_type": "human", "first_name": "Admin", "last_name": "User" }, "org": { "id": "22222222222222222222", "name": "Example Org" } }, "current_api_version": "1.0" } ``` **Notes:** - Returns a more limited view than the authenticated version. - If the profile or owner cannot be loaded, `owner` will be `null`. --- ### POST /current/user/invitations/acceptall/ Accept all pending invitations. **Auth:** Required (JWT) **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `invitation_key` | string | No | Optional invitation key. If the user's email is not validated, this key can identify invitations. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/invitations/acceptall/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Notes:** - If email is validated, all pending invitations matching that email are accepted. - If email is not validated, use `invitation_key` to identify invitations. --- ### GET /current/user/invitations/list/ List all pending invitations for the current user. **Auth:** Required (JWT) **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `invitation_key` | string | No | Optional invitation key for users without validated email. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/invitations/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "invitations": [ { "id": "12345678901234567890", "entity": "98765432101234567890", "state": "pending", "email": "jane.doe@example.com" } ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.invitations` | array | Array of invitation resource objects. | --- ## User Authentication Endpoints ### GET /current/user/auth/ Authenticate via HTTP Basic Auth. Returns JWT token. **Auth:** HTTP Basic Auth (`email:password`) **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `expires` | integer | No | Server default | Custom JWT expiration time in seconds. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/auth/" \ -u "jane.doe@example.com:SecureP@ss123" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "expires_in": 86400, "auth_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "2factor": false }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.expires_in` | integer | JWT token expiration time in seconds. | | `response.auth_token` | string | JWT access token. If 2FA is enabled, has `twofactor` scope (restricted). Otherwise has `user` scope (full access). | | `response.2factor` | boolean | `true` if 2FA is enabled. Token has limited scope until 2FA verification is completed. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1651 (Invalid Request Type)` | 400 | "The expires time specified is invalid." | Invalid `expires` parameter | | `1650 (Authentication Invalid)` | 401 | "Your credentials were not supplied or invalid." | Missing Basic Auth header | | `1650 (Authentication Invalid)` | 401 | "Username is not valid." | Invalid email format | | `1650 (Authentication Invalid)` | 401 | "Password is not valid." | Invalid password format | | `1653 (User Not Found)` | 404 | "The username was not found." | Email not registered | | `1650 (Authentication Invalid)` | 401 | "The password on this account is not set, use SSO." | SSO-only account (no password set) | | `1650 (Authentication Invalid)` | 401 | "Your credentials supplied are invalid." | Wrong password | | `1650 (Authentication Invalid)` | 401 | "Your account is suspended..." | Account suspended | | `1650 (Authentication Invalid)` | 401 | "Your account is locked..." | Account locked | | `1650 (Authentication Invalid)` | 401 | "Your account is suspended due to abuse." | Account flagged for abuse | | `1650 (Authentication Invalid)` | 401 | "Your account is closed by you." | Account closed | **Notes:** - Email tags (e.g., `user+tag@example.com`) are stripped before lookup. - If 2FA is enabled, complete the 2FA verification flow to upgrade the token. - SSO-only accounts cannot use this endpoint. --- ### GET /current/user/auth/check/ Validate current JWT and get user ID. **Auth:** Required (Bearer token) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/auth/check/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "id": "12345678901234567890" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.id` | string | The 19-digit numeric user ID. | **Notes:** - Lightweight health-check for token validity. Only validates that the JWT is structurally valid and not expired. --- ### GET /current/auth/scopes/ Token scope introspection. Returns information about the current token's scope, auth type, and agent status. **Auth:** Required (Bearer token) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/auth/scopes/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "auth_type": "jwt_v2", "scopes": ["org:12345:rw", "org:67890:rw"], "scopes_detail": [], "is_agent": true, "agent_name": "My MCP Agent", "full_access": false }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.auth_type` | string | Token type: `"jwt_v2"` (scoped JWT), `"jwt_v1"` (legacy JWT), `"api_key"` (unscoped API key), or `"api_key_scoped"` (API key with scopes). | | `response.scopes` | array | Array of scope strings in `entity_type:entity_id:access_mode` format. Empty for v1 JWTs and unscoped API keys. Populated for scoped API keys. | | `response.scopes_detail` | array | Hydrated scope details with entity information. Empty when scopes are empty. | | `response.is_agent` | boolean | Whether the token represents an agent. | | `response.agent_name` | string or null | Agent display name. `null` if not set or not an agent. | | `response.full_access` | boolean | Whether the token has unrestricted access. `true` for v1 JWTs and unscoped API keys. `false` for scoped API keys. | --- ### API Keys #### POST /current/user/auth/key/ Create a new API key. **Auth:** Required (JWT, scope: `user` or `admin`) **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `memo` | string | No | Label/description for the key. | | `scopes` | string | No | JSON array of scope strings (e.g., `["org:123:rw", "workspace:456:r"]`). Omit or `null` for full access. | | `agent_name` | string | No | Agent or application name for tracking. Max 128 characters. | | `expires` | string | No | Expiration datetime (`YYYY-MM-DD HH:MM:SS` or ISO 8601 e.g., `2026-12-31T23:59:59Z`). Omit or `null` for no expiration. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/auth/key/" \ -H "Authorization: Bearer {jwt_token}" \ -d "memo=CI/CD Pipeline Key" ``` **Request Example (scoped key with expiration):** ```bash curl -X POST "https://api.fast.io/current/user/auth/key/" \ -H "Authorization: Bearer {jwt_token}" \ -d "memo=Workspace Agent" \ -d 'scopes=["workspace:1234567890123456789:rw"]' \ -d "agent_name=my-agent" \ -d "expires=2026-12-31T23:59:59Z" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "api_key": "abcdefghij1234567890abcdefghij12" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.api_key` | string | The newly created API key. **Only shown once** -- store it securely. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Your credentials were not supplied or invalid." | Missing or invalid JWT | | `1650 (Authentication Invalid)` | 401 | "The scope of your credentials are not sufficient." | JWT scope not `user` or `admin` | | `1654 (Internal Error)` | 500 | "You are at the maximum number of API keys, {max}." | Maximum key limit reached | | `1605 (Invalid Input)` | 400 | "You provided an invalid Memo." | Invalid memo format | **Notes:** - 2FA verification is required if 2FA is enabled. - The full key value is only returned at creation time. Subsequent reads return masked versions. --- #### GET /current/user/auth/key/{key_id}/ Get details of an API key (key value is masked). **Auth:** Required (JWT, scope: `user` or `admin`) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{key_id}` | string | Yes | The API key's unique identifier. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/auth/key/key_12345/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "api_key": { "id": "key_12345", "api_key": "****************************ab12", "memo": "CI/CD Pipeline Key", "created": "2024-01-15 10:30:00 UTC", "scopes": "[\"workspace:1234567890123456789:rw\"]", "agent_name": "my-agent", "expires": "2026-12-31 23:59:59 UTC" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.api_key.id` | string | Unique key identifier. | | `response.api_key.api_key` | string | Masked API key (only last 4 characters visible). | | `response.api_key.memo` | string | Key description/label. | | `response.api_key.created` | string | Key creation timestamp in UTC. | | `response.api_key.scopes` | string or null | JSON array of scope strings, or `null` for full access. | | `response.api_key.agent_name` | string or null | Agent/application name, or `null` if not set. | | `response.api_key.expires` | string or null | Expiration datetime (`YYYY-MM-DD HH:MM:SS`), or `null` for no expiration. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid Token to get details of." | Invalid key ID format | | `1609 (Not Found)` | 404 | Key not found | Key does not exist | --- #### POST /current/user/auth/key/{key_id}/ Update an existing API key's memo, scopes, agent_name, and/or expires. **Auth:** Required (JWT, scope: `user` or `admin`) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{key_id}` | string | Yes | The API key's unique identifier. | **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `memo` | string | No | Updated label/description for the key. | | `scopes` | string | No | JSON array of scope strings. Send empty string or `"null"` to clear (restore full access). | | `agent_name` | string | No | Agent/application name. Send empty string or `"null"` to clear. Max 128 characters. | | `expires` | string | No | Expiration datetime (`YYYY-MM-DD HH:MM:SS` or ISO 8601). Send empty string or `"null"` to clear (no expiration). | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/auth/key/key_12345/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'scopes=["org:1234567890123456789:r"]' \ -d "agent_name=updated-agent" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "api_key": { "id": "key_12345", "api_key": "****************************ab12", "memo": "CI/CD Pipeline Key", "created": "2024-01-15 10:30:00 UTC", "scopes": "[\"org:1234567890123456789:r\"]", "agent_name": "updated-agent", "expires": null } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid Token to update." | Invalid key ID format | | `1609 (Not Found)` | 404 | Key not found | Key does not exist or belongs to another user | | `1605 (Invalid Input)` | 400 | Various | Invalid scopes, agent_name, or expires format | **Notes:** - Only the fields you send are updated; omitted fields remain unchanged. - Send empty string or `"null"` to clear a nullable field. - 2FA verification is required if 2FA is enabled. --- #### DELETE /current/user/auth/key/{key_id}/ Delete an API key. **Auth:** Required (JWT, scope: `user` or `admin`) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{key_id}` | string | Yes | The API key's unique identifier. | **Request Example:** ```bash curl -X DELETE "https://api.fast.io/current/user/auth/key/key_12345/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You provided an invalid Token to Delete." | Invalid key ID format | | `1609 (Not Found)` | 404 | "You provided a Token that was not found." | Key not found or belongs to another user | | `1610 (Delete Failed)` | 500 | "There was an error deleting the API Key." | Internal deletion failure | **Notes:** - 2FA verification is required if 2FA is enabled. - Returns "not found" if the key belongs to a different user (does not reveal ownership). --- #### GET /current/user/auth/keys/ List all API keys for the user. **Auth:** Required (JWT) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/auth/keys/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "results": 2, "api_keys": [ { "id": "key_12345", "api_key": "****************************ab12", "memo": "CI/CD Pipeline Key", "created": "2024-01-15 10:30:00 UTC", "scopes": "[\"workspace:1234567890123456789:rw\"]", "agent_name": "my-agent", "expires": "2026-12-31 23:59:59 UTC" }, { "id": "key_67890", "api_key": "****************************cd34", "memo": "Backup Script", "created": "2024-02-20 14:00:00 UTC", "scopes": null, "agent_name": null, "expires": null } ] }, "current_api_version": "1.0" } ``` **No Keys Response (200 OK):** ```json { "result": "yes", "response": { "results": 0, "api_keys": null }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.results` | integer | Number of API keys. | | `response.api_keys` | array or null | Array of API key objects, or `null` if none exist. | | `response.api_keys[].id` | string | Unique key identifier. | | `response.api_keys[].api_key` | string | Masked API key (only last 4 characters visible). | | `response.api_keys[].memo` | string | Key description/label. | | `response.api_keys[].created` | string | Key creation timestamp in UTC. | | `response.api_keys[].scopes` | string or null | JSON array of scope strings, or `null` for full access. | | `response.api_keys[].agent_name` | string or null | Agent/application name, or `null` if not set. | | `response.api_keys[].expires` | string or null | Expiration datetime (`YYYY-MM-DD HH:MM:SS`), or `null` for no expiration. | --- ### Two-Factor Authentication (2FA) #### GET /current/user/auth/2factor/ Get current 2FA status. **Auth:** Required (JWT, scope: `user` or `admin`) **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/auth/2factor/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "state": "enabled", "totp": false }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.state` | string | 2FA status: `"enabled"` (fully verified), `"unverified"` (added but not verified), or `"disabled"` (not configured). | | `response.totp` | boolean | Whether the 2FA method is TOTP (Time-based One-Time Password). | --- #### POST /current/user/auth/2factor/{channel}/ Enable 2FA on the account. **Auth:** Required (JWT, scope: `user` or `admin`) **Path Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `{channel}` | string | No | `sms` | 2FA delivery channel: `sms`, `call`, `whatsapp`, or `totp`. | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/auth/2factor/sms/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response for SMS/Voice/WhatsApp (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Success Response for TOTP (202 Accepted):** ```json { "result": "yes", "response": { "binding_uri": "otpauth://totp/fast.io:jane@example.com?secret=ABCDEF..." }, "current_api_version": "1.0" } ``` **Response Fields (TOTP only):** | Field | Type | Description | |-------|------|-------------| | `response.binding_uri` | string | TOTP provisioning URI for QR code display. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1669 (Already Exists)` | 409 | "2Factor already added, please remove first." | 2FA already enabled | | `1605 (Invalid Input)` | 400 | "An invalid channel was supplied." | Invalid channel name | | `1658 (Not Acceptable)` | 406 | "2Factor cannot be added, you need a valid phone_number and phone_country..." | No phone number configured | **Notes:** - User must have a valid phone number and country code on their account before enabling 2FA (for non-TOTP channels). - After adding 2FA, it enters `unverified` state. Must complete verification via `POST /current/user/auth/2factor/verify/{token}/`. - For TOTP, display the `binding_uri` as a QR code for the user to scan. --- #### POST /current/user/auth/2factor/verify/{token}/ Verify a 2FA setup code to confirm enrollment. Transitions 2FA from `unverified` to `enabled` state. **Auth:** Required (JWT) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{token}` | string | Yes | 2FA verification code (e.g., 6-digit code). | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/auth/2factor/verify/123456/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Verification Failed (406 Not Accepted):** ```json { "result": "no", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid token was supplied to validate." | Invalid token format | | `1658 (Not Acceptable)` | 406 | "2Factor is not enabled." | 2FA not configured | **Notes:** - If 2FA is already in the `enabled` state, returns success without modification. - This is the final step of the 2FA setup flow. --- #### POST /current/user/auth/2factor/auth/{token}/ Authenticate with a 2FA code. Upgrades a limited-scope JWT to a full-scope JWT. **Auth:** Required (JWT, scope: `user`, `twofactor`, or `admin`) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{token}` | string | Yes | Valid 2FA verification code (e.g., 6-digit TOTP or SMS code). | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/user/auth/2factor/auth/123456/" \ -H "Authorization: Bearer {twofactor_jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "expires_in": 86400, "auth_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.expires_in` | integer | JWT expiration time in seconds. | | `response.auth_token` | string | New JWT with full `user` scope. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid token was supplied to authenticate." | Invalid token format | | `1658 (Not Acceptable)` | 406 | "2Factor is not enabled on this account." | 2FA not enabled | | `1658 (Not Acceptable)` | 406 | "The supplied token failed to authenticate." | Wrong 2FA code | | `1650 (Authentication Invalid)` | 401 | "Internal Error." | JWT creation failure | --- #### DELETE /current/user/auth/2factor/{token}/ Disable (remove) 2FA from the account. **Auth:** Required (JWT, scope: `user` or `admin`) **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{token}` | string | Yes | Valid 2FA verification code. Required only if 2FA is in `enabled` (verified) state. | **Request Example:** ```bash curl -X DELETE "https://api.fast.io/current/user/auth/2factor/123456/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid token was supplied, valid token required to remove 2Factor." | Invalid token format | | `1658 (Not Acceptable)` | 406 | "The supplied token failed to authenticate." | Token verification failed | | `1654 (Internal Error)` | 500 | "2Factor could not be removed, please contact support." | Internal removal failure | **Notes:** - If 2FA is `enabled` (verified), a valid 2FA code is required to remove it. - If 2FA is `unverified`, it can be removed without a code. - If 2FA is already disabled, returns success. --- #### 2FA Code Delivery Endpoints Request a 2FA code via different channels. All require auth (accepts `user`, `twofactor`, or `admin` JWT scope). **GET /current/user/auth/2factor/send/sms/** -- Send code via SMS ```bash curl -X GET "https://api.fast.io/current/user/auth/2factor/send/sms/" \ -H "Authorization: Bearer {jwt_token}" ``` **GET /current/user/auth/2factor/send/call/** -- Send code via voice call ```bash curl -X GET "https://api.fast.io/current/user/auth/2factor/send/call/" \ -H "Authorization: Bearer {jwt_token}" ``` **GET /current/user/auth/2factor/send/whatsapp/** -- Send code via WhatsApp ```bash curl -X GET "https://api.fast.io/current/user/auth/2factor/send/whatsapp/" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Failure Response (406 Not Accepted):** ```json { "result": "no", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Your credentials were not supplied or invalid." | Invalid JWT | | `1650 (Authentication Invalid)` | 401 | "The scope of your credentials are not sufficient." | Wrong JWT scope | | `1658 (Not Acceptable)` | 406 | "2Factor is not enabled." | 2FA not configured on account | **Notes:** - 2FA must be enabled (or in unverified state) for codes to be sent. - Returns `result: "no"` if the code send fails (e.g., invalid phone number). --- ### Complete 2FA Login Flow ``` 1. GET /current/user/auth/ - Send email:password via HTTP Basic Auth - Response includes "2factor": true and limited-scope auth_token 2. GET /current/user/auth/2factor/send/sms/ (or /call/ or /whatsapp/) - Request a fresh 2FA code - Uses the limited-scope (twofactor) JWT 3. POST /current/user/auth/2factor/auth/{code}/ - Submit the 2FA code - Receive a new JWT with full "user" scope - Use this token for all subsequent requests ``` ### Complete 2FA Setup Flow ``` 1. POST /current/user/auth/2factor/{channel}/ - Choose channel: sms, call, whatsapp, or totp - Phone number must be configured on account (for non-TOTP) - State becomes "unverified" - For TOTP: receive binding_uri for QR code 2. Receive code via selected channel (or scan QR code for TOTP) 3. POST /current/user/auth/2factor/verify/{code}/ - Submit the verification code - State becomes "enabled" - 2FA is now active on the account ``` ### Complete 2FA Removal Flow ``` 1. DELETE /current/user/auth/2factor/{code}/ - Must provide valid 2FA code if state is "enabled" - Can remove without code if state is "unverified" - 2FA is fully removed from the account ``` --- ## User Search ### GET /current/users/search/ Search for users by name or email across contacts and the platform user directory. **Auth:** Required (JWT) **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `search` | string | Yes | Search term. Matches against user names and email addresses. Must not be blank. | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/users/search/?search=john" \ -H "Authorization: Bearer {jwt_token}" ``` **Success Response (200 OK):** ```json { "result": "yes", "response": { "contacts": { "john.doe@example.com": "John Doe", "jane.johnson@example.com": "Jane Johnson" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.contacts` | object | Map of email address (key) to display name (value) for each matched user. | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid JWT token | | `1605 (Invalid Input)` | 400 | "This value should not be blank." | Missing or empty `search` parameter | | `1654 (Internal Error)` | 500 | "Internal error" | Search backend unavailable | **Notes:** - Searches across two sources: the user's contacts and the platform user directory. Results are merged and deduplicated by email. - Response format is a flat key-value map (email -> name), not an array of objects. --- ## Response Envelope **Success:** ```json {"result": "yes", "current_api_version": "1.0", ...} ``` **Error:** ```json { "result": "no", "error": { "code": 195654, "text": "Human-readable message", "documentation_url": "https://api.fast.io/llms.txt", "resource": "POST /current/user/" } } ``` ## Common Error Codes | Code | Description | HTTP Status | |------|-------------|-------------| | 1600 | Internal Error | 500 Internal Server Error | | 1605 | Invalid Input | 400 Bad Request | | 1606 | Not Acceptable | 406 Not Acceptable | | 1607 | Duplicate Entry / Already Exists | 409 Conflict | | 1608 | Conflict | 409 Conflict | | 1609 | Not Found / Resource Missing | 404 Not Found | | 1610 | Delete Failed | 500 Internal Server Error | | 1650 | Authentication Invalid | 401 Unauthorized | | 1651 | Invalid Request Type | 400 Bad Request | | 1653 | User Not Found | 404 Not Found | | 1671 | Rate Limited | 429 Too Many Requests | | 1680 | Access Denied | 403 Forbidden | | 1681 | Restricted | 403 Forbidden | | 1682 | Locked | 423 Locked | | 1690 | SSO Auth Error | 401 Unauthorized | ## Rate Limiting Response headers: `X-Rate-Limit-Available`, `X-Rate-Limit-Expiry`, `X-Rate-Limit-Max` When exceeded: HTTP 429 with error code 1671 (Rate Limited). ## ID Formats - User IDs: 19-digit numeric string (e.g., `"1234567890123456789"`) - `"me"` can be used as user_id in user endpoints to reference the authenticated user - User endpoints also accept email address as an identifier ## Token Types | Type | Format | Lifetime | Use | |------|--------|----------|-----| | JWT (Basic Auth) | RS256-signed JSON Web Token | Configurable (default varies) | General API access | | JWT (OAuth) | RS256-signed JSON Web Token | 1 hour | OAuth-based API access | | Refresh Token | Opaque string | 30 days | Obtaining new access tokens (OAuth only) | | API Key | Alphanumeric string | Configurable (default: no expiry) | Service-to-service communication. Optionally scoped with permissions, agent name, and expiration. | ## Security Best Practices 1. Always use HTTPS for all API communication. 2. Store refresh tokens and API keys securely (OS keychain, encrypted storage). 3. Never log tokens in client-side logs or analytics. 4. Rotate refresh tokens -- always store the new token from a refresh response. 5. Verify the `state` parameter in OAuth callbacks to prevent CSRF. 6. Handle 401 responses by attempting a token refresh; if refresh fails, re-authenticate. 7. Revoke tokens on logout by calling the revoke endpoint and clearing local storage. > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # OAuth 2.0 Base URL: `https://api.fast.io/current/` Request format: `application/x-www-form-urlencoded` (unless noted otherwise) Response format: JSON --- ## Overview Fast.io implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure authorization without passing user credentials through the client application. This is the recommended auth method for desktop apps, mobile apps, and MCP-connected agents. **Key characteristics:** - S256 challenge method only (plain not supported) - Access tokens last 1 hour (3600 seconds) - Refresh tokens last 30 days - Authorization codes last 5 minutes - Authorization requests last 10 minutes - Supports SSO (user signs in via browser, supports federated login) - No user password passes through the agent/client - Dynamic Client Registration (RFC 7591/7592) for automated client onboarding - Client ID Metadata Document (CIMD) for URL-based client identification - Resource Indicators (RFC 8707) for audience-restricted JWT access tokens - Scoped access tokens (JWT v2.0) with entity-level restrictions and agent identity - Metadata discovery (RFC 8414, RFC 9728) for automated server/resource configuration --- ## Endpoint Summary | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/.well-known/oauth-authorization-server/` | None | RFC 8414 authorization server metadata | | GET | `/.well-known/oauth-protected-resource/` | None | RFC 9728 protected resource metadata | | POST | `/current/oauth/register/` | None | RFC 7591 dynamic client registration | | PUT | `/current/oauth/register/` | Registration access token (Bearer) | RFC 7592 update client registration | | GET | `/current/oauth/authorize/` | None | Initiate auth flow -- 302 redirect to login page (or JSON with `response_format=json`) | | POST | `/current/oauth/authorize/` | Session (JWT) | Complete authorization, issue code | | GET | `/current/oauth/authorize/info/` | None | Get client info for consent screen | | POST | `/current/oauth/token/` | None | Exchange code for tokens, or refresh tokens | | POST | `/current/oauth/revoke/` | None | Revoke a refresh token | | GET | `/current/oauth/sessions/` | Bearer | List all active sessions | | GET | `/current/oauth/sessions/{id}/` | Bearer | Get session details | | PATCH | `/current/oauth/sessions/{id}/` | Bearer | Update session display names | | DELETE | `/current/oauth/sessions/{id}/` | Bearer | Revoke a specific session | | DELETE | `/current/oauth/sessions/` | Bearer | Revoke all sessions | | GET | `/current/auth/scopes/` | Bearer | Token scope introspection | --- ## Metadata Discovery ### GET /.well-known/oauth-authorization-server/ RFC 8414 Authorization Server Metadata. Returns server configuration for automated client setup. **Auth:** None ```bash curl -X GET "https://api.fast.io/.well-known/oauth-authorization-server/" ``` **Response (200 OK):** ```json { "issuer": "https://go.fast.io", "authorization_endpoint": "https://go.fast.io/api/current/oauth/authorize/", "token_endpoint": "https://go.fast.io/api/current/oauth/token/", "revocation_endpoint": "https://go.fast.io/api/current/oauth/revoke/", "registration_endpoint": "https://go.fast.io/api/current/oauth/register/", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "token_endpoint_auth_methods_supported": ["none"], "code_challenge_methods_supported": ["S256"], "resource_indicators_supported": true, "service_documentation": "https://fast.io/docs" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `issuer` | string | Authorization server issuer identifier URL | | `authorization_endpoint` | string | URL of the authorization endpoint | | `token_endpoint` | string | URL of the token endpoint | | `revocation_endpoint` | string | URL of the token revocation endpoint | | `registration_endpoint` | string | URL of the dynamic client registration endpoint | | `response_types_supported` | array | Supported response types (`code` only) | | `grant_types_supported` | array | Supported grant types (`authorization_code`, `refresh_token`) | | `token_endpoint_auth_methods_supported` | array | Supported auth methods (`none` -- public clients only) | | `code_challenge_methods_supported` | array | Supported PKCE methods (`S256` only) | | `resource_indicators_supported` | boolean | RFC 8707 support (`true`) | | `service_documentation` | string | URL to service documentation | **Error Responses:** | Scenario | HTTP Status | Error | |----------|-------------|-------| | Wrong HTTP method | 400 | `1651 (Invalid Request Type)` | --- ### GET /.well-known/oauth-protected-resource/ RFC 9728 Protected Resource Metadata. Returns resource server configuration. **Auth:** None ```bash curl -X GET "https://api.fast.io/.well-known/oauth-protected-resource/" ``` **Response (200 OK):** ```json { "resource": "https://mcp.fast.io/mcp", "authorization_servers": ["https://go.fast.io"], "bearer_methods_supported": ["header"], "scopes_supported": ["user"] } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `resource` | string | Protected resource identifier URL | | `authorization_servers` | array | Authorization server issuer URLs that can issue tokens for this resource | | `bearer_methods_supported` | array | Methods for presenting bearer tokens (`header` -- Authorization header only) | | `scopes_supported` | array | OAuth scopes supported by this resource | **Error Responses:** | Scenario | HTTP Status | Error | |----------|-------------|-------| | Wrong HTTP method | 400 | `1651 (Invalid Request Type)` | --- ## Dynamic Client Registration ### POST /current/oauth/register/ RFC 7591 Dynamic Client Registration. Register a new OAuth client programmatically. **Auth:** None **Content-Type:** `application/json` or `application/x-www-form-urlencoded` **Request Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `client_name` | string | No | `"Unknown Client"` | Max 128 characters | Human-readable client name | | `redirect_uris` | JSON array | Yes | -- | 1-10 URIs. HTTPS required (localhost/127.0.0.1 exempt). No fragment (`#`) components. | Allowed redirect URIs | | `token_endpoint_auth_method` | string | No | `"none"` | Must be `"none"` | Auth method (only public clients supported) | | `grant_types` | JSON array | No | `["authorization_code", "refresh_token"]` | -- | Requested grant types | | `response_types` | JSON array | No | `["code"]` | -- | Requested response types | ```bash curl -X POST "https://api.fast.io/current/oauth/register/" \ -H "Content-Type: application/json" \ -d '{ "client_name": "My MCP Client", "redirect_uris": ["http://localhost:3000/callback", "http://127.0.0.1:3000/callback"] }' ``` **Response (200 OK):** ```json { "result": true, "response": { "client_id": "dyn_a1b2c3d4e5f6g7h8i9j0", "client_name": "My MCP Client", "redirect_uris": [ "http://localhost:3000/callback", "http://127.0.0.1:3000/callback" ], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "registration_access_token": "rat_a1b2c3d4e5f6g7h8...", "registration_client_uri": "https://api.fast.io/current/oauth/register/" } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.client_id` | string | Assigned client identifier for OAuth flows | | `response.client_name` | string | Registered client name | | `response.redirect_uris` | array | Registered redirect URIs | | `response.token_endpoint_auth_method` | string | Token endpoint auth method (`"none"`) | | `response.grant_types` | array | Granted grant types | | `response.response_types` | array | Granted response types | | `response.registration_access_token` | string | One-time token for managing registration via PUT. Shown **once only** -- store securely. Server stores only a SHA-256 hash. | | `response.registration_client_uri` | string | URI for managing this client registration | **Error Responses (RFC 7591 format -- bare JSON, no platform envelope):** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `invalid_client_metadata` | 400 | `client_name` exceeds 128 characters | | `invalid_client_metadata` | 400 | `redirect_uris` is not a valid JSON array | | `invalid_client_metadata` | 400 | `token_endpoint_auth_method` is not `"none"` | | `invalid_redirect_uri` | 400 | `redirect_uris` must contain 1-10 entries | | `invalid_redirect_uri` | 400 | Redirect URI is not a valid URL | | `invalid_redirect_uri` | 400 | Redirect URI contains a fragment (`#`) | | `invalid_redirect_uri` | 400 | Redirect URI must use HTTPS (except localhost) | | `invalid_request` | 400 | `redirect_uris` is missing | | `server_error` | 500 | Failed to register client | --- ### PUT /current/oauth/register/ RFC 7592 Dynamic Client Registration Management. Update an existing client registration. Requires the `registration_access_token` from the POST registration response. Only clients whose redirect URIs all point to `localhost`, `127.0.0.1`, `[::1]`, or `::1` may self-update. **Auth:** Registration access token (`Authorization: Bearer {registration_access_token}`) or `registration_access_token` body parameter **Content-Type:** `application/json` or `application/x-www-form-urlencoded` **Request Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `client_id` | string | Yes | Must match a registered client | Client ID to update | | `client_name` | string | No | Max 128 characters | Updated client name | | `redirect_uris` | JSON array | No | Same validation as POST | Updated redirect URIs (full replacement) | At least one of `client_name` or `redirect_uris` must be provided. ```bash curl -X PUT "https://api.fast.io/current/oauth/register/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer rat_a1b2c3d4e5f6g7h8..." \ -d '{ "client_id": "dyn_a1b2c3d4e5f6g7h8i9j0", "client_name": "My Updated MCP Client", "redirect_uris": ["http://localhost:8080/callback"] }' ``` **Response (200 OK):** ```json { "result": true, "response": { "client_id": "dyn_a1b2c3d4e5f6g7h8i9j0", "client_name": "My Updated MCP Client", "redirect_uris": ["http://localhost:8080/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"] } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.client_id` | string | Client identifier (unchanged) | | `response.client_name` | string | Updated client name | | `response.redirect_uris` | array | Updated redirect URIs | | `response.token_endpoint_auth_method` | string | Auth method (unchanged, always `"none"`) | | `response.grant_types` | array | Grant types (unchanged) | | `response.response_types` | array | Response types (unchanged) | **Error Responses (RFC 7591 format -- bare JSON):** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `invalid_request` | 400 | `client_id` missing | | `invalid_request` | 400 | Neither `client_name` nor `redirect_uris` provided | | `invalid_request` | 400 | Only localhost clients can self-update | | `invalid_request` | 401 | Invalid or missing registration access token | | `invalid_client` | 404 | Client not found or disabled | | `invalid_client_metadata` | 400 | Invalid client metadata | | `invalid_redirect_uri` | 400 | Invalid redirect URIs (same rules as POST) | | `server_error` | 500 | Failed to update registration | **Notes:** - The `grant_types`, `response_types`, and `token_endpoint_auth_method` fields cannot be changed via self-update. - If `redirect_uris` is provided, it fully replaces the existing set. - If only `client_name` is provided, existing redirect URIs are preserved. --- ## Client ID Metadata Document (CIMD) CIMD allows OAuth clients to use an HTTPS URL as their `client_id` instead of pre-registering or using Dynamic Client Registration. The authorization server fetches metadata from the URL to get client information on-the-fly. This is the MCP specification's preferred client registration method. ### How It Works 1. **Detection:** If the `client_id` parameter starts with `https://`, the server treats it as a CIMD URL. 2. **Fetch:** The server fetches the JSON metadata document from the CIMD URL. 3. **Validation:** The document must contain: - `client_id` matching the fetched URL exactly - `redirect_uris` array containing the requested redirect URI - `grant_types` including `authorization_code` - `response_types` including `code` - `token_endpoint_auth_method` set to `none` 4. **Caching:** Validated documents are cached to avoid repeated fetches. 5. **Flow:** The CIMD URL is used as the `client_id` throughout the authorization flow (authorize, token exchange, refresh). ### CIMD Document Format The metadata document is a JSON file served at an HTTPS URL with `Content-Type: application/json`: ```json { "client_id": "https://example.com/oauth/client-metadata", "client_name": "Example App", "redirect_uris": ["http://localhost:8080/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` Optional fields: `client_uri` (URL to the client's home page), `logo_uri` (URL to the client's logo image). ### Security Constraints | Constraint | Value | |-----------|-------| | Protocol | HTTPS only (HTTP URLs rejected) | | Fetch timeout | 5 seconds | | Max document size | 10 KB | | Cross-domain redirects | Rejected | | Cache duration | 1 hour | ### Using CIMD in the Authorization Flow Use the CIMD URL directly as the `client_id` parameter: ``` GET /current/oauth/authorize/?client_id=https://example.com/oauth/client-metadata&redirect_uri=http://localhost:8080/callback&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=xyz123 ``` The token endpoint also uses the CIMD URL as `client_id` -- it matches by string comparison against the value stored during authorization. ### Error Scenarios | Scenario | Error | |----------|-------| | CIMD URL is not HTTPS | `1605 (Invalid Input)` | | Cannot fetch the document (timeout, DNS failure, non-200 response) | `1605 (Invalid Input)` | | Document is not valid JSON | `1605 (Invalid Input)` | | `client_id` in document does not match the URL | `1605 (Invalid Input)` | | Required fields missing or invalid | `1605 (Invalid Input)` | | `redirect_uri` not found in document's `redirect_uris` | `1605 (Invalid Input)` | --- ## Complete PKCE Flow ### Step 0: Discover Server Configuration (Optional) Fetch server metadata for automated setup: ``` 1. CLIENT -> API: Discover authorization server GET /.well-known/oauth-authorization-server/ -> Returns endpoints, supported grant types, PKCE methods 2. CLIENT -> API: Discover protected resource (if needed) GET /.well-known/oauth-protected-resource/ -> Returns resource URL and authorization servers ``` ### Step 0b: Register Client (If Needed) If the client does not have a registered `client_id`, there are two options: **Option A: Use a CIMD URL as client_id** -- If the client publishes a metadata document at an HTTPS URL, use that URL directly as the `client_id`. No registration step needed. **Option B: Use Dynamic Client Registration:** ``` CLIENT -> API: Register client POST /current/oauth/register/ Content-Type: application/json {"client_name": "My Agent", "redirect_uris": ["http://localhost:8080/callback"]} -> Returns client_id, redirect_uris, registration_access_token, etc. ``` ### Step 1: Generate PKCE Parameters (Client-Side) Before initiating the flow, generate the PKCE code verifier and challenge: ``` code_verifier = random_string(43-128 characters, URL-safe: [A-Za-z0-9-._~]) code_challenge = base64url_encode(sha256(code_verifier)) code_challenge_method = "S256" ``` The `code_challenge` will always be exactly 43 characters. ### Step 2: Initiate Authorization #### GET /current/oauth/authorize/ Initiates the authorization flow. By default, issues a `302 Found` redirect to the login/consent page. This is the browser-facing entry point that MCP clients open in the user's browser. **Auth:** None **JSON mode:** Add `response_format=json` to receive a JSON response instead of a redirect (for programmatic callers). **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `client_id` | string | Yes | -- | -- | Registered OAuth client ID, or an HTTPS URL pointing to a CIMD | | `redirect_uri` | string | Yes | -- | Must match a registered URI for the client | Client callback URI | | `response_type` | string | Yes | -- | Must be `"code"` | OAuth response type | | `code_challenge` | string | Yes | -- | Exactly 43 characters (BASE64URL-encoded SHA-256) | PKCE code challenge | | `code_challenge_method` | string | Yes | -- | Must be `"S256"` | PKCE challenge method | | `state` | string | Yes | -- | -- | Opaque value for CSRF protection, returned unchanged in callback | | `scope` | string | No | `"user"` | See scope values table | Scope type selector | | `resource` | string | No | -- | Must be a valid resource URL (e.g., `https://mcp.fast.io/mcp`) | RFC 8707 resource indicator | | `agent_name` | string | No | -- | Max 128 characters | Display name of the requesting agent | | `response_format` | string | No | -- | `"json"` | Set to `"json"` for JSON response instead of 302 redirect | **Scope type values (`scope` parameter):** These are **named scope strings** used only in the authorization request. They are not the same as the scope format returned in API responses. | Value | Behavior | |-------|----------| | `user` | Full access (default, backward compatible with v1.0 JWT) | | `org` | User picks specific organizations | | `workspace` | User picks specific workspaces | | `all_orgs` | Wildcard access to all user's organizations | | `all_workspaces` | Wildcard access to all user's workspaces | | `all_shares` | All shares the user is a member of | **Scope format in responses:** API responses (e.g., `GET /current/auth/scopes/` and `POST /current/oauth/token/`) return scopes as arrays of `entity_type:entity_id:access_mode` strings (e.g., `["org:12345:rw"]`). The token endpoint returns `scopes` as a JSON-encoded string that must be parsed. See the Token Scope Introspection section below for the full response format. ```bash # Standard browser flow (302 redirect) curl -v "https://api.fast.io/current/oauth/authorize/?client_id=my-app&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=abc123xyz" # JSON mode (programmatic) curl "https://api.fast.io/current/oauth/authorize/?client_id=my-app&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=abc123xyz&response_format=json" ``` **Response (default -- 302 Found):** ``` Location: https://go.fast.io/connect?auth_request_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 ``` **Response (response_format=json -- 200 OK):** ```json { "result": true, "response": { "auth_request_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "client_name": "My App", "scope": "user" } } ``` **Response Fields (JSON mode):** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.auth_request_id` | string | 64-character hex identifier for this authorization request (valid for 10 minutes) | | `response.client_name` | string | Human-readable name of the OAuth client application | | `response.scope` | string | The granted scope | | `response.agent_name` | string | Agent display name (present if `agent_name` was provided) | **Error Responses (standard platform envelope):** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | `client_id` missing | `1605 (Invalid Input)` | 400 | | `redirect_uri` missing | `1605 (Invalid Input)` | 400 | | `code_challenge` missing | `1605 (Invalid Input)` | 400 | | `code_challenge_method` missing | `1605 (Invalid Input)` | 400 | | `code_challenge_method` not `"S256"` | `1605 (Invalid Input)` | 400 | | `code_challenge` not 43 characters | `1605 (Invalid Input)` | 400 | | `state` missing | `1605 (Invalid Input)` | 400 | | `client_id` not found | `1605 (Invalid Input)` | 400 | | Client is disabled | `1605 (Invalid Input)` | 400 | | `redirect_uri` mismatch | `1605 (Invalid Input)` | 400 | | Invalid `resource` indicator | `1605 (Invalid Input)` | 400 | | Internal storage failure | `1654 (Internal Error)` | 500 | --- #### POST /current/oauth/authorize/ Complete authorization after user login. Issues the authorization code. Called by the web application (not the client directly) with the user's active session. **Auth:** Required (JWT -- website session) **Request Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `auth_request_id` | string | Yes | -- | 64-character hex | The authorization request ID from the GET request | | `scopes` | string | No | -- | JSON array of entity IDs or scope strings | Entity IDs for scoped access (e.g., `"[12345, 67890]"`) or explicit scope strings (e.g., `'["org:12345:rw"]'`) | | `access_mode` | string | No | `"rw"` | `"r"` (read-only) or `"rw"` (read-write) | Access level for scoped tokens | | `agent_name` | string | No | -- | Max 128 characters | Override the agent display name from the GET request | ```bash curl -X POST "https://api.fast.io/current/oauth/authorize/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "auth_request_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ``` **Response (200 OK):** ```json { "result": true, "response": { "redirect_uri": "http://localhost:8080/callback?code=abc123def456abc123def456abc123def456abc123def456abc123def456abc123de&state=abc123xyz", "redirect_mode": "redirect" } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.redirect_uri` | string | Full redirect URI with `code` (64 hex chars, valid 5 min, single-use) and `state` query parameters | | `response.redirect_mode` | string | `"redirect"` for HTTP/HTTPS redirect URIs, `"button"` for custom scheme URIs | | `response.button_label` | string or null | Custom button label for `"button"` redirect mode (if configured) | **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | User not authenticated | `1650 (Authentication Invalid)` | 401 | | `auth_request_id` missing | `1605 (Invalid Input)` | 400 | | Request expired or not found | `1609 (Not Found)` | 404 | | Internal processing error | `1654 (Internal Error)` | 500 | | Invalid `access_mode` | `1605 (Invalid Input)` | 400 | | Scope validation failed | `1605 (Invalid Input)` | 400 | --- #### GET /current/oauth/authorize/info/ Validate an authorization request and return client information (app name, requested scope). Used by the web frontend to display a consent screen before the user confirms. **Auth:** None **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `auth_request_id` | string | Yes | The authorization request ID to validate | ```bash curl "https://api.fast.io/current/oauth/authorize/info/?auth_request_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ``` **Response (valid request -- 200 OK):** ```json { "result": true, "response": { "valid": true, "client_name": "My App", "scope": "user", "agent_name": "My MCP Agent", "redirect_mode": "redirect", "redirect_uri": "http://localhost:8080/callback" } } ``` **Response (invalid or expired -- 200 OK):** ```json { "result": true, "response": { "valid": false } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | Always `true` | | `response.valid` | boolean | `true` if the auth request is valid and client is active | | `response.client_name` | string | Client name (only when `valid` is `true`) | | `response.scope` | string | Requested scope (only when `valid` is `true`) | | `response.agent_name` | string | Agent display name (only when set and `valid` is `true`) | | `response.redirect_mode` | string | `"redirect"` or `"button"` (only when `valid` is `true`) | | `response.button_label` | string | Custom button label (only when configured and `valid` is `true`) | | `response.redirect_uri` | string | Client redirect URI (only when `valid` is `true`) | **Notes:** - This endpoint never returns an error for invalid `auth_request_id`. It returns `valid: false` instead, preventing information leakage about authorization request existence. --- ### Step 3: User Approves in Browser The user opens the authorization URL, signs in (supports SSO), and approves access. The browser either: - Redirects to `redirect_uri` with `?code={authorization_code}&state={state}` - Displays the authorization code for the user to copy back to the agent (when `redirect_mode` is `"button"`) ### Step 4: Exchange Code for Tokens #### POST /current/oauth/token/ Exchange an authorization code for access and refresh tokens, or refresh an existing access token. **Auth:** None **Content-Type:** `application/x-www-form-urlencoded` **Important:** This endpoint returns **bare JSON responses** (RFC 6749 format, no platform envelope) for compatibility with standard OAuth clients, including MCP hosts. ##### Authorization Code Exchange **Request Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `grant_type` | string | Yes | Must be `"authorization_code"` | Grant type | | `code` | string | Yes | 64-character hex | Authorization code from the callback | | `code_verifier` | string | Yes | 43-128 characters, `[A-Za-z0-9-._~]` | Original PKCE code verifier | | `client_id` | string | Yes | Must match original request | OAuth client ID | | `redirect_uri` | string | Yes | Must match original request | Redirect URI | | `resource` | string | No | Must exactly match the value from the authorize request | RFC 8707 resource indicator URL | | `device_name` | string | No | -- | Human-readable device name for session tracking | | `device_type` | string | No | -- | Device category for session tracking | ```bash curl -X POST "https://api.fast.io/current/oauth/token/" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=abc123def456abc123def456abc123def456abc123def456abc123def456abc123de&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&client_id=my-app&redirect_uri=http://localhost:8080/callback" ``` **Response (200 OK -- bare JSON, no envelope):** ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...", "scope": "org", "scopes": "[\"org:12345:rw\",\"org:67890:rw\"]", "agent_name": "My MCP Agent" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `access_token` | string | JWT for API requests. Use as `Authorization: Bearer {access_token}`. Expires in 1 hour. | | `token_type` | string | Always `"Bearer"` | | `expires_in` | integer | Token lifetime in seconds (3600 = 1 hour) | | `refresh_token` | string | Long-lived token for obtaining new access tokens. Valid for 30 days. Store securely -- only returned once, stored as hash. | | `scope` | string | Granted scope type | | `scopes` | string | JSON-encoded array of scope strings in `entity_type:entity_id:access_mode` format (v2.0 tokens only, absent for `user` scope without explicit scopes) | | `agent_name` | string | Agent display name (v2.0 only, present when set during authorization) | ##### Refresh Token Exchange | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `grant_type` | string | Yes | Must be `"refresh_token"` | | `refresh_token` | string | Yes | Current valid refresh token | | `client_id` | string | Yes | Must match the original token request | | `device_name` | string | No | Updated device name for session tracking | | `device_type` | string | No | Updated device type for session tracking | ```bash curl -X POST "https://api.fast.io/current/oauth/token/" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token&refresh_token=dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...&client_id=my-app" ``` **Response (200 OK -- bare JSON):** Same format as authorization code exchange. Returns a new `access_token` and a **new** `refresh_token`. The old refresh token is immediately revoked (mandatory token rotation). **Error Responses (RFC 6749 SS5.2 format -- bare JSON):** ```json { "error": "invalid_grant", "error_description": "The authorization code is invalid or has expired." } ``` | Scenario | RFC 6749 Error | HTTP Status | |----------|---------------|-------------| | Missing/invalid parameters | `invalid_request` | 400 | | Invalid `grant_type` value | `unsupported_grant_type` | 400 | | `code_verifier` invalid length (not 43-128 chars) | `invalid_request` | 400 | | Unrecognized `resource` indicator | `invalid_request` | 400 | | Invalid/expired authorization code | `invalid_grant` | 400 | | PKCE `code_verifier` mismatch | `invalid_grant` | 400 | | `client_id` mismatch | `invalid_grant` | 400 | | `redirect_uri` mismatch | `invalid_grant` | 400 | | Resource indicator mismatch (RFC 8707) | `invalid_grant` | 400 | | Invalid/expired/revoked refresh token | `invalid_grant` | 400 | | `client_id` mismatch on refresh | `invalid_grant` | 400 | | Inactive user account | `invalid_grant` | 400 | | Internal server failure | `server_error` | 500 | **Resource Indicator Enforcement:** The `resource` value is stored in the authorization code during the authorize step. At token exchange, the value is strictly compared. If they do not match -- including if one is `null` and the other is not -- the exchange fails. This prevents downgrade attacks where a client omits the resource to obtain an unrestricted token. --- ### Step 5: Use the Access Token Include the access token in all API requests: ``` Authorization: Bearer {access_token} ``` When the access token expires (after 1 hour), use the refresh token to get a new one without requiring user interaction. Refresh proactively before expiration (5-minute buffer recommended) to avoid interruptions. --- ## Token Revocation ### POST /current/oauth/revoke/ Revoke a refresh token (logout). Implements RFC 7009 (OAuth 2.0 Token Revocation). Always returns success to prevent token enumeration attacks. **Auth:** None **Content-Type:** `application/x-www-form-urlencoded` **Request Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `token` | string | Yes | The refresh token to revoke | ```bash curl -X POST "https://api.fast.io/current/oauth/revoke/" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..." ``` **Response (200 OK):** ```json { "result": true, "response": { "result": true } } ``` **Error Responses (RFC 6749 format -- bare JSON):** | Scenario | Error | HTTP Status | |----------|-------|-------------| | `token` parameter missing | `invalid_request` | 400 | **Notes:** - Per RFC 7009, this endpoint always returns success regardless of whether the token was found, was already revoked, or never existed. - Always call this endpoint on user logout and clear local token storage regardless of the response. --- ## Session Management OAuth sessions represent active token grants. Each authorization code exchange creates a session. Sessions have a stable `session_id` (32-character hex string) that persists across token rotations. ### GET /current/oauth/sessions/ List all active (non-expired, non-revoked) OAuth sessions for the authenticated user. **Auth:** Required (Bearer JWT) ```bash curl -X GET "https://api.fast.io/current/oauth/sessions/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response": { "sessions": [ { "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "client_id": "my-app", "device_name": "Chrome on macOS", "device_type": "desktop", "ip_address": "203.0.113.42", "last_used": "2026-01-22 14:00:00", "created": "2026-01-21 14:00:00", "expires": "2026-02-21 14:00:00" } ], "count": 1 } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.sessions` | array | List of active session objects | | `response.sessions[].session_id` | string | 32-character hex session identifier | | `response.sessions[].client_id` | string | OAuth client that created the session | | `response.sessions[].device_name` | string | Human-readable device description | | `response.sessions[].device_type` | string | Device category: `desktop`, `mobile`, `tablet`, or `unknown` | | `response.sessions[].ip_address` | string | IP address at session creation | | `response.sessions[].last_used` | string | Last token refresh timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.sessions[].created` | string | Session creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.sessions[].expires` | string | Session expiration timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.count` | integer | Total number of active sessions | **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | Internal error | `1654 (Internal Error)` | 500 | --- ### GET /current/oauth/sessions/{session_id}/ Get details of a specific OAuth session. The session must belong to the authenticated user. **Auth:** Required (Bearer JWT) **Path Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `session_id` | string | Yes | 32 hexadecimal characters | Session identifier | ```bash curl -X GET "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response": { "session": { "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "client_id": "my-app", "device_name": "Chrome on macOS", "device_type": "desktop", "ip_address": "203.0.113.42", "last_used": "2026-01-22 14:00:00", "created": "2026-01-21 14:00:00", "expires": "2026-02-21 14:00:00" } } } ``` **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | `session_id` missing | `1605 (Invalid Input)` | 400 | | `session_id` not 32 hex chars | `1605 (Invalid Input)` | 400 | | Session not found or wrong user | `1609 (Not Found)` | 404 | | Internal error | `1654 (Internal Error)` | 500 | --- ### PATCH /current/oauth/sessions/{session_id}/ Update the `device_name` and/or `agent_name` of a specific OAuth session. The session must belong to the authenticated user and must not be revoked. **Auth:** Required (Bearer JWT) **Path Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `session_id` | string | Yes | 32 hexadecimal characters | Session identifier | **Body Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `device_name` | string | No | Max 128 characters. Empty string clears to null. | New device name | | `agent_name` | string | No | Max 128 characters. Empty string clears to null. | New agent name | At least one of `device_name` or `agent_name` must be provided. ```bash curl -X PATCH "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "device_name=My%20Work%20Laptop" ``` **Response (200 OK):** ```json { "result": true, "response": { "session": { "session_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "client_id": "my-app", "device_name": "My Work Laptop", "device_type": "desktop", "ip_address": "203.0.113.42", "last_used": "2026-01-22 14:00:00", "created": "2026-01-21 14:00:00", "expires": "2026-02-21 14:00:00" } } } ``` **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | `session_id` missing | `1605 (Invalid Input)` | 400 | | `session_id` not 32 hex chars | `1605 (Invalid Input)` | 400 | | Neither parameter provided | `1605 (Invalid Input)` | 400 | | `device_name` exceeds 128 chars | `1605 (Invalid Input)` | 400 | | `agent_name` exceeds 128 chars | `1605 (Invalid Input)` | 400 | | Session is revoked | `1605 (Invalid Input)` | 400 | | Session not found or wrong user | `1609 (Not Found)` | 404 | | Internal error | `1654 (Internal Error)` | 500 | --- ### DELETE /current/oauth/sessions/{session_id}/ Revoke a specific OAuth session. The session must belong to the authenticated user. This operation is idempotent -- if the session is already revoked, success is still returned. **Auth:** Required (Bearer JWT) **Path Parameters:** | Parameter | Type | Required | Constraints | Description | |-----------|------|----------|-------------|-------------| | `session_id` | string | Yes | 32 hexadecimal characters | Session identifier | ```bash curl -X DELETE "https://api.fast.io/current/oauth/sessions/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response": { "result": true, "message": "Session has been revoked." } } ``` **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | `session_id` missing | `1605 (Invalid Input)` | 400 | | `session_id` not 32 hex chars | `1605 (Invalid Input)` | 400 | | Session not found or wrong user | `1609 (Not Found)` | 404 | | Internal error | `1654 (Internal Error)` | 500 | --- ### DELETE /current/oauth/sessions/ Revoke all OAuth sessions (logout everywhere). Optionally exclude the current session for "log out everywhere else" functionality. **Auth:** Required (Bearer JWT) **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `exclude_current` | string | No | -- | Set to `"true"` or `"1"` to keep the current session active | | `current_session_id` | string | No | -- | Session ID to preserve (required when `exclude_current` is set) | ```bash # Revoke ALL sessions curl -X DELETE "https://api.fast.io/current/oauth/sessions/" \ -H "Authorization: Bearer {jwt_token}" # Revoke all EXCEPT current session curl -X DELETE "https://api.fast.io/current/oauth/sessions/?exclude_current=true¤t_session_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response": { "result": true, "message": "All sessions have been revoked." } } ``` Or when `exclude_current` is used: ```json { "result": true, "response": { "result": true, "message": "All other sessions have been revoked." } } ``` **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | Internal error | `1654 (Internal Error)` | 500 | **Notes:** - When `exclude_current` is `"true"` but `current_session_id` is not provided, all sessions are revoked. --- ## Token Scope Introspection ### GET /current/auth/scopes/ Returns the current token's scope information, auth type, and agent status. Enables clients to discover their token's capabilities without decoding the JWT. **Auth:** Required (Bearer JWT or API Key) ```bash curl -X GET "https://api.fast.io/current/auth/scopes/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response": { "auth_type": "jwt_v2", "scopes": ["org:12345:rw", "org:67890:rw"], "scopes_detail": [ { "entity_type": "org", "entity_id": "12345", "access_mode": "rw", "name": "Acme Corp", "domain": "acme" }, { "entity_type": "org", "entity_id": "67890", "access_mode": "rw", "name": "Beta Inc", "domain": "beta" } ], "is_agent": true, "agent_name": "My MCP Agent", "full_access": false } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | boolean | `true` on success | | `response.auth_type` | string | `"jwt_v2"` (scoped JWT), `"jwt_v1"` (legacy JWT), or `"api_key"` | | `response.scopes` | array | Scope strings in `entity_type:entity_id:access_mode` format. Empty for v1.0 JWT and API key. | | `response.scopes_detail` | array | Hydrated scope objects with entity names and metadata. Empty for v1.0 JWT and API key. | | `response.is_agent` | boolean | Whether the token represents an agent | | `response.agent_name` | string or null | Agent display name if set, otherwise `null` | | `response.full_access` | boolean | `true` for v1.0 JWT, API keys, and v2.0 tokens with `user:*:rw` scope | **Scope Detail Fields:** Each entry in `scopes_detail` contains entity-specific fields: | Field | Type | Present For | Description | |-------|------|-------------|-------------| | `entity_type` | string | All | `user`, `org`, `workspace`, or `share` | | `entity_id` | string | All | Numeric ID or `*` for wildcard | | `access_mode` | string | All | `r` (read) or `rw` (read/write) | | `label` | string | Full access / wildcard | Human-readable label (e.g., "Full Access", "All Organizations") | | `name` | string | Org, Workspace | Entity display name | | `domain` | string | Org | Organization subdomain | | `folder_name` | string | Workspace | Workspace URL slug | | `org_id` | integer | Workspace, Share | Parent organization ID | | `org_name` | string | Workspace, Share | Parent organization name | | `org_domain` | string | Workspace, Share | Parent organization subdomain | | `title` | string | Share | Share display title | | `share_type` | string | Share | `send`, `receive`, or `exchange` | | `workspace_id` | integer | Share | Parent workspace ID | | `workspace_name` | string | Share | Parent workspace name | | `load_error` | string | On failure | `"Entity not found"` if the referenced entity could not be loaded | **Error Responses:** | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Not authenticated | `1650 (Authentication Invalid)` | 401 | | Wrong HTTP method | `1651 (Invalid Request Type)` | 400 | --- ## Complete PKCE Example Flow Here is a complete example of the PKCE authorization flow: ``` 0. CLIENT -> API: Discover server configuration (optional) GET /.well-known/oauth-authorization-server/ -> Returns endpoints, grant types, PKCE methods, resource_indicators_supported 0b. CLIENT -> API: Register client dynamically (if no client_id) POST /current/oauth/register/ Content-Type: application/json {"client_name": "My Agent", "redirect_uris": ["http://localhost:8080/callback"]} -> Returns client_id, registration_access_token OR use a CIMD URL as client_id (no registration needed) 1. CLIENT: Generate PKCE parameters code_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" code_challenge = base64url(sha256(code_verifier)) = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" 2. CLIENT -> BROWSER: Open authorization URL in user's browser GET /current/oauth/authorize/ ?client_id=my-app &redirect_uri=http://localhost:8080/callback &response_type=code &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &state=xyz123 &resource=https://mcp.fast.io/mcp 3. API -> BROWSER: 302 redirect to login/consent page -> Browser lands on login page, user signs in, approves access 4. BROWSER -> CLIENT: Authorization code returned http://localhost:8080/callback?code=AUTH_CODE_HERE&state=xyz123 (or displayed on screen for user to copy) 5. CLIENT -> API: Exchange code for tokens POST /current/oauth/token/ Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTH_CODE_HERE &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk &client_id=my-app &redirect_uri=http://localhost:8080/callback &resource=https://mcp.fast.io/mcp 6. API -> CLIENT: Tokens returned (bare JSON) { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "eyJ...", "scope": "user" } 7. CLIENT -> API: Use access token for requests GET /current/user/me/details/ Authorization: Bearer eyJ... 8. CLIENT -> API: Refresh when access token expires POST /current/oauth/token/ Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=eyJ... &client_id=my-app -> Returns new access_token + new refresh_token (old one revoked) 9. CLIENT -> API: Revoke on logout POST /current/oauth/revoke/ Content-Type: application/x-www-form-urlencoded token=eyJ... ``` --- ## Error Handling ### Token and Revoke Endpoints (RFC 6749 SS5.2) The token (`POST /current/oauth/token/`) and revoke (`POST /current/oauth/revoke/`) endpoints return RFC 6749 compliant error responses -- bare JSON, not the standard platform envelope: ```json { "error": "invalid_grant", "error_description": "The authorization code is invalid or has expired." } ``` ### Registration Endpoint (RFC 7591) The registration endpoint (`POST /current/oauth/register/` and `PUT /current/oauth/register/`) also returns bare JSON error responses with RFC 7591 error codes: ```json { "error": "invalid_client_metadata", "error_description": "The client_name must be between 1 and 128 characters." } ``` ### Other OAuth Endpoints All other OAuth endpoints (authorize, authorize/info, sessions) use the standard platform error envelope: ```json { "result": false, "error": { "code": 1605, "text": "Error message", "documentation_url": "https://api.fast.io/llms.txt", "resource": "GET /current/oauth/authorize/" } } ``` | Scenario | Error Code | HTTP Status | |----------|-----------|-------------| | Rate limited | `1671 (Rate Limited)` | 429 | > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Organization Management Base URL: `https://api.fast.io/current/` All authenticated endpoints require: `Authorization: Bearer {jwt_token}` An organization (org) is a collector of workspaces. It can represent a company, a business unit, a team, or simply a personal collection. Orgs are the billable entity -- storage, credits, and member limits are tracked at the org level. Every workspace and share lives under an org. Profile IDs are 19-digit numeric strings. Most endpoints also accept the org's domain name (e.g., `acme`) in place of the numeric ID. --- ## Internal vs External Orgs Agents **must call both** `GET /current/orgs/list/` and `GET /current/orgs/list/external/` to discover all orgs they can access. - **Internal orgs** (`member: true`) -- orgs you created or were invited to join as a member. You have org-level access: see all workspaces (subject to permissions), manage settings if admin, appear in the member list. Listed via `GET /current/orgs/list/`. - **External orgs** (`member: false`) -- orgs you access only through workspace membership. A human invited you to their workspace but not to the org itself. You can see the org's name and basic public info, but cannot manage org settings, see other workspaces, or add org members. Listed via `GET /current/orgs/list/external/`. **External orgs are the most common pattern** when a human invites an agent to help with a specific project -- they add the agent to a workspace but not to the org itself. If the human later invites the agent to the org itself, it moves from external to internal and gains org-level access. --- ## Org Field Constraints | Field | Type | Min | Max | Regex / Rules | Default | |-------|------|-----|-----|---------------|---------| | `domain` | string | 2 | 80 | `^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$` Lowercase alphanumeric + hyphens. Must be unique. Must not be reserved. | Required | | `name` | string | 3 | 100 | Free text display name | `null` | | `description` | string | 10 | 1000 | Free text | `null` | | `industry` | string | -- | -- | Must be one of the values from `GET /current/orgs/industries/` | `null` | | `perm_member_manage` | string | -- | -- | `'Member or above'`, `'Admin or above'`, `'Owner only'` | `'Member or above'` | | `perm_authorized_domains` | string | -- | -- | Email domain for auto-join (e.g., `acme.com`) | `null` | | `billing_email` | string (email) | -- | -- | Valid email with reachable domain | User's email | | `accent_color` | string (JSON) | -- | -- | JSON-encoded color object | `null` | | `background_color` | string (JSON) | -- | -- | JSON-encoded color object | `null` | | `background_mode` | string | -- | -- | One of the supported background display modes | `null` | --- ## Member Roles and Permissions | Role | Level | Can manage members | Can manage settings | Can manage billing | Can close org | Can transfer ownership | |------|-------|-------------------|--------------------|--------------------|--------------|----------------------| | Owner | Highest | Yes | Yes | Yes | Yes | Yes | | Admin | High | Yes (if `perm_member_manage` allows) | Yes | Yes | No | No | | Member | Standard | If `perm_member_manage = 'Member or above'` | No | No | No | No | | View | Lowest | No | No | No | No | No | The `perm_member_manage` org setting controls the minimum role required to add, remove, or update members. --- ## Organization CRUD ### Create Organization ``` POST /current/org/create/ ``` Auth required. Creates a new organization. The authenticated user becomes the owner. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `domain` | string | Yes | 2-80 chars, lowercase alphanumeric + hyphens, must be unique and not reserved. Used as the org identifier in URLs. | | `name` | string | No | 3-100 chars. Display name for the org. | | `description` | string | No | Organization description. | | `industry` | string | No | Industry type from predefined list (see `GET /current/orgs/industries/`). | | `accent_color` | string (JSON) | No | Brand accent color as JSON. | | `background_color` | string (JSON) | No | Background color as JSON. | | `background_mode` | string | No | Background display mode. | | `facebook_url` | string (URL) | No | Facebook page URL. Must be valid URL. | | `twitter_url` | string (URL) | No | Twitter profile URL. Must be valid URL. | | `instagram_url` | string (URL) | No | Instagram profile URL. Must be valid URL. | | `youtube_url` | string (URL) | No | YouTube channel URL. Must be valid URL. | | `homepage_url` | string (URL) | No | Organization website URL. Must be valid URL. | | `perm_member_manage` | string | No | Who can manage members. See Org Field Constraints above. | | `perm_authorized_domains` | string | No | Authorized email domain for auto-join. | | `billing_email` | string (email) | No | Billing contact email. Defaults to user's email. | Agent accounts are always assigned the `agent` plan (free: 50 GB storage, 5,000 credits/month, no trial, no expiration). Agents cannot select or change to a human plan. **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/create/" \ -H "Authorization: Bearer {jwt_token}" \ -d "domain=acme-corp" \ -d "name=Acme Corporation" \ -d "industry=technology" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "org": { "id": "1234567890123456789", "domain": "acme-corp", "name": "Acme Corporation", "description": null, "logo": null, "accent_color": null, "closed": false, "suspended": false }, "has_free_trial": true, "requires_payment": false, "is_agent": false } } ``` When user is ineligible for free trial: ```json { "result": "yes", "response": { "org": { "..." : "..." }, "has_free_trial": false, "requires_payment": true, "no_trial_reason": "existing_org" } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `org` | object | Organization resource object | | `org.id` | string | 19-digit numeric organization ID | | `org.domain` | string | URL-safe subdomain | | `org.name` | string/null | Display name | | `org.description` | string/null | Description | | `org.logo` | string/null | Logo asset URL | | `org.accent_color` | string/null | Brand color | | `org.closed` | boolean | Whether org is closed | | `org.suspended` | boolean | Whether org is suspended | | `has_free_trial` | boolean | Whether the org has a free trial period | | `requires_payment` | boolean | Whether payment is required before activation | | `is_agent` | boolean | Whether the creating user is an agent | | `no_trial_reason` | string | Reason free trial was denied (only when `requires_payment` is true) | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid org domain was supplied." | Invalid domain format | | `1605 (Invalid Input)` | 400 | "The supplied org domain name is restricted." | Domain is reserved | | `1605 (Invalid Input)` | 400 | "The supplied org domain name is already in use." | Domain already taken | | `1605 (Invalid Input)` | 400 | "An invalid configuration was supplied..." | Metadata validation failed | | `1605 (Invalid Input)` | 400 | "Invalid JSON provided for {key}." | Malformed JSON in color fields | | `1663 (Update Failed)` | 400 | "There was an internal error processing your create request." | Org creation failed | | `1654 (Internal Error)` | 500 | "There was an internal error processing your create request." | Storage instance creation failed | | `1654 (Internal Error)` | 500 | "We were unable to create your organization..." | Internal error | | `1680 (Access Denied)` | 403 | GEO/risk restriction message | Request blocked by geo/risk check | --- ### Get Org Details ``` GET /current/org/{org_id}/details/ ``` Auth required. Returns full org details. Fields vary by the requesting user's permission level. `{org_id}` accepts a 19-digit numeric ID or the org's domain name. **Access levels:** | Role | Access | Notes | |------|--------|-------| | Owner | Full access | Full access to all organization settings and security configuration. | | Admin | Extended access | Includes billing info, permissions, subscriber status, credit balance | | Member | Standard access | Basic org info, plan, subscriber status (boolean only — no credit balance) | | View | Limited access | Public fields only | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "org": { "id": "1234567890123456789", "domain": "acme-corp", "name": "Acme Corporation", "description": "Leading provider of innovation", "logo": "https://assets.fast.io/org/logo.png", "accent_color": "#0066CC", "closed": false, "locked": false, "suspended": false, "created": "2024-01-15 10:30:00", "updated": "2024-06-20 14:45:00" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `org.id` | string | 19-digit numeric organization ID | | `org.domain` | string | URL-safe subdomain | | `org.name` | string/null | Display name | | `org.description` | string/null | Description | | `org.logo` | string/null | Logo asset URL | | `org.accent_color` | string/null | Brand color | | `org.closed` | boolean | Whether org is closed | | `org.locked` | boolean | Whether org is locked | | `org.suspended` | boolean | Whether org is suspended | | `org.created` | string | Creation timestamp | | `org.updated` | string | Last update timestamp | | `org.plan` | string | Billing plan identifier (e.g., `"free"`, `"agent"`, `"pro"`). Member+ only. | | `org.subscriber` | boolean | Whether the org has an active subscription (includes credit availability for free-tier orgs). Member+ only. | | `org.subscriber_trial_until` | integer/null | Unix timestamp when the trial period ends. `null` for paid plans or if no trial. Member+ only. | | `org.subscriber_trial_credits` | integer/null | Credits remaining in the current billing period. `null` for unlimited (paid) plans. Admin+ only. | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "You have not been granted access to this Org." | Insufficient permission | | `1688 (Subscription Required)` | 402 | "The organization does not have an active subscription." | Org has no active subscription or free-tier credits exhausted | | `1696 (Credit Limit Exceeded)` | 402 | "You have exceeded your credit limit." | Free-tier credit limit exceeded (includes usage details) | --- ### Get Public Org Details ``` GET /current/org/{org_id}/public/details/ ``` No authentication required. Returns limited public info about an org (name, domain, assets). IP-rate-limited. `{org_id}` accepts a 19-digit numeric ID or the org's domain name. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/public/details/" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "org": { "id": "1234567890123456789", "domain": "acme-corp", "name": "Acme Corporation", "description": "Leading provider of innovation", "logo": "https://assets.fast.io/org/logo.png", "accent_color": "#0066CC" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `org.id` | string | 19-digit numeric organization ID | | `org.domain` | string | URL-safe subdomain | | `org.name` | string/null | Display name | | `org.description` | string/null | Description | | `org.logo` | string/null | Logo asset URL | | `org.accent_color` | string/null | Brand color | --- ### Update Organization ``` POST /current/org/{org_id}/update/ ``` Auth required. Admin or above. Updates org details. Only provided fields are modified. **Access levels:** | Role | Access | |------|--------| | Owner | Full access | | Admin | Full access | | Member | Denied | **Request parameters (all optional):** | Name | Type | Description | |------|------|-------------| | `domain` | string | New URL-safe subdomain (2-80 chars, lowercase alphanumeric + hyphens). | | `name` | string | Display name (3-100 chars). Send `"null"` to clear. | | `description` | string | Description. Send `"null"` or `""` to clear. | | `industry` | string | Industry type from predefined list. | | `accent_color` | string (JSON) | Brand accent color as JSON. Send `"null"` to clear. | | `background_color` | string (JSON) | Background color as JSON. Send `"null"` to clear. | | `background_mode` | string | Background display mode. | | `use_background` | string | Enable/disable background (`"true"`/`"false"`). | | `facebook_url` | string (URL) | Facebook URL. | | `twitter_url` | string (URL) | Twitter URL. | | `instagram_url` | string (URL) | Instagram URL. | | `youtube_url` | string (URL) | YouTube URL. | | `homepage_url` | string (URL) | Organization website URL. | | `perm_member_manage` | string | Member management permission level. | | `perm_authorized_domains` | string | Authorized email domain for auto-join. | | `billing_email` | string (email) | Billing contact email. Domain must be reachable. | | `owner_defined` | string (JSON) | Custom owner-defined properties. Send `"null"` or `""` to clear. | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=Acme Corp Updated" \ -d "description=Updated description" \ -d "industry=technology" ``` **Response (200 OK):** ```json { "result": "yes" } ``` If no actual changes are detected, returns success immediately. **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid org domain was supplied." | Invalid domain format | | `1605 (Invalid Input)` | 400 | "The supplied org domain name is restricted." | Domain is reserved | | `1605 (Invalid Input)` | 400 | "The supplied org domain name is already in use." | Domain taken by another org | | `1605 (Invalid Input)` | 400 | "An invalid configuration was supplied..." | Metadata validation failed | | `1605 (Invalid Input)` | 400 | "Invalid JSON provided for {key}." | Malformed JSON | | `1605 (Invalid Input)` | 400 | "The email domain is invalid or cannot receive email." | Bad billing email domain | | `1663 (Update Failed)` | 400 | "There was an internal error processing your update request." | Internal error | --- ### Close Organization ``` POST /current/org/{org_id}/close/ ``` Auth required. Owner only. Soft-deletes the organization. Active subscriptions are automatically cancelled. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `confirm` | string | Yes | Must match the org domain name or org numeric ID as confirmation. | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/close/" \ -H "Authorization: Bearer {jwt_token}" \ -d "confirm=acme-corp" ``` **Response (202 Accepted):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "The `confirm` field provided does not match." | Confirmation does not match domain or ID | | `1663 (Update Failed)` | 400 | "There was an internal error processing your request." | Failed to close org | Storage deletion is deferred to the deletion system after a retention period. --- ## Organization Assets ### List Available Asset Types ``` GET /current/org/assets/ ``` Auth required. Returns available org asset metadata types (e.g., logo, background images). **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/assets/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "assets": [ { "name": "logo", "mime_types": ["image/png", "image/jpeg"], "max_size": 5242880 } ] } } ``` --- ### List Org Assets ``` GET /current/org/{org_id}/assets/ ``` Auth required. Any member with at least View permission. Returns assets currently set on the org. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/assets/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "assets": { "logo": { "url": "https://assets.fast.io/org/logo.png", "mime_type": "image/png", "size": 45678 } } } } ``` --- ### Upload Org Asset ``` POST /current/org/{org_id}/assets/{asset_name}/ ``` Auth required. Admin or above. Upload as multipart/form-data. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | file | file (multipart) | Yes | The asset file to upload. | | `metadata` | string (JSON array) | No | Additional metadata for the asset. | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/assets/logo/" \ -H "Authorization: Bearer {jwt_token}" \ -F "file=@logo.png" ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1604 (File Missing)` | 400 | "Asset upload missing" | No file in the request | | `1605 (Invalid Input)` | 400 | "metadata invalid" | Metadata is not a valid array | --- ### Delete Org Asset ``` DELETE /current/org/{org_id}/assets/{asset_name}/ ``` Auth required. Admin or above. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/assets/logo/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` --- ### Read Org Asset (Raw) ``` GET /current/org/{org_id}/assets/{asset_name}/read/ ``` No authentication required. Returns the raw binary content of an org asset with appropriate `Content-Type` header. Useful for displaying logos and images directly. HEAD requests return headers only. --- ## Organization Members ### Add or Invite a Member ``` POST /current/org/{org_id}/members/{email_or_user_id}/ ``` Auth required. Permission governed by org's `perm_member_manage` setting. The target is specified as a path parameter: - Use a **user ID** (19-digit numeric) to add an existing user directly - Use an **email address** to send an invitation **Request parameters (for adding an existing user by ID):** | Name | Type | Required | Description | |------|------|----------|-------------| | `permissions` | string | Yes | Permission level: `"member"`, `"admin"`. Cannot add as `"owner"`. | | `expires` | string (datetime) | No | Membership expiration date. | | `notify` | string | No | Notification preference. | | `force_notification` | boolean | No | Force send a notification email. | **Request parameters (for inviting by email):** | Name | Type | Required | Description | |------|------|----------|-------------| | `permissions` | string | Yes | Permission level for the invitation: `"member"`, `"admin"`. | | `message` | string | No | Custom invitation message. | | `expires` | string (datetime) | No | Invitation expiration. | **curl example (add existing user):** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/members/9876543210987654321/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=member" ``` **curl example (invite by email):** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/members/jane@example.com/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=member" \ -d "message=Welcome to the team!" ``` **Response (200 OK) -- direct add:** ```json { "result": "yes" } ``` **Response (200 OK) -- invitation created:** ```json { "result": "yes", "response": { "invitation": { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "invitee_email": "jane@example.com", "entity_type": "org", "state": "pending", "created": "2024-01-15 10:30:00" } } } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Invalid permission specified." | Invalid permission value | | `1680 (Cannot Add As Owner)` | 403 | "Adding a member as an owner is not allowed" | Tried to add as owner (use transfer_ownership) | | `1656 (Limit Exceeded)` | 429 | Limit message | Member limit exceeded | --- ### Remove a Member ``` DELETE /current/org/{org_id}/members/{user_id}/ ``` Auth required. Permission governed by org's `perm_member_manage` setting. The target user ID (19-digit numeric) is a path parameter. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/members/9876543210987654321/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` --- ### List Org Members ``` GET /current/org/{org_id}/members/list/ ``` Auth required. Any org member. Paginated. **Query parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `limit` | integer | 100 | 1-500, number of items to return | | `offset` | integer | 0 | Number of items to skip | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/members/list/?limit=50&offset=0" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "users": [ { "id": "1234567890123456789", "account_type": "human", "email_address": "owner@example.com", "first_name": "John", "last_name": "Doe", "permissions": "owner" }, { "id": "1234567890123456780", "account_type": "agent", "email_address": "bot@example.com", "first_name": "Service", "last_name": "Bot", "permissions": "admin" } ], "pagination": { "total": 2, "limit": 50, "offset": 0, "has_more": false } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `users` | array | Array of member objects | | `users[].id` | string | 19-digit numeric user ID | | `users[].account_type` | string | `"human"` or `"agent"` | | `users[].email_address` | string | User's email | | `users[].first_name` | string | First name | | `users[].last_name` | string | Last name | | `users[].permissions` | string | Role: `"owner"`, `"admin"`, `"member"` | | `pagination.total` | integer | Total number of members | | `pagination.limit` | integer | Requested page size | | `pagination.offset` | integer | Current offset | | `pagination.has_more` | boolean | Whether more results exist | --- ### Leave Organization (Self) ``` DELETE /current/org/{org_id}/member/ ``` Auth required. Removes the authenticated user from the org. Owners cannot leave -- they must transfer ownership or close the org first. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/member/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You cannot leave an org you are the owner of..." | User is the org owner | | `1605 (Invalid Input)` | 400 | "You cannot leave an org you are not a member of." | User is not a member | --- ### Get Member Details ``` GET /current/org/{org_id}/member/{user_id}/details/ ``` Auth required. Any org member. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/member/9876543210987654321/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "member": { "id": "9876543210987654321", "account_type": "human", "email_address": "jane@example.com", "first_name": "Jane", "last_name": "Smith", "permissions": "admin", "invite": "accepted", "notify": "Email me", "expires": null } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `member.id` | string | 19-digit numeric user ID | | `member.account_type` | string | `"human"` or `"agent"` | | `member.email_address` | string | User's email | | `member.first_name` | string | First name | | `member.last_name` | string | Last name | | `member.permissions` | string | Role: `"owner"`, `"admin"`, `"member"` | | `member.invite` | string | Invitation status | | `member.notify` | string | Notification preference | | `member.expires` | string/null | Membership expiration (`YYYY-MM-DD HH:MM:SS`) or null for no expiry | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | User is not a member | --- ### Update Member Permissions ``` POST /current/org/{org_id}/member/{user_id}/update/ ``` Auth required. Permission governed by org's `perm_member_manage` setting. **Request parameters (all optional):** | Name | Type | Description | |------|------|-------------| | `permissions` | string | New permission level (`"member"`, `"admin"`) | | `expires` | string (datetime) | Membership expiration date | | `notify` | string | Notification preference | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/member/9876543210987654321/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=admin" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | User is not a member | --- ### Transfer Org Ownership ``` GET /current/org/{org_id}/member/{user_id}/transfer_ownership/ ``` Auth required. Owner only. Transfers ownership of the org to the specified member. The current owner is demoted to admin. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/member/9876543210987654321/transfer_ownership/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You cannot transfer ownership to yourself." | Target is the current user | | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | User is not an org member | | `1605 (Invalid Input)` | 400 | "Member is already an owner." | Target is already the owner | | `1663 (Update Failed)` | 400 | "Failed to update owner of the org." | Internal error | --- ### Join Organization ``` POST /current/org/{org_id}/members/join/ ``` Auth required. Join an org via invite or domain-based auto-join. **Join methods:** 1. **Via invitation:** Append the invitation key to the URL path: `.../join/{invitation_key}/` optionally followed by `accept` or `decline`. Default is `accept`. 2. **Via authorized domain:** The org must have `perm_authorized_domains` set and the user's email domain must match. User is added as a Member. **curl example (invitation):** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/members/join/abc123def456/accept/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "This org does not allow you to join automatically..." | Domain auto-join not enabled | | `1680 (Access Denied)` | 403 | "You are not allowed to join this org automatically..." | User's email domain does not match | | `1656 (Limit Exceeded)` | 429 | Limit message | Member limit exceeded | --- ### List Org Invitations ``` GET /current/org/{org_id}/members/invitations/list/ ``` Auth required. Any org member. An optional state filter can be appended: `.../list/pending/`. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/members/invitations/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "invitations": [ { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "inviter": "John Doe", "invitee_email": "jane@example.com", "entity_type": "org", "state": "pending", "created": "2024-01-15 10:30:00", "expires": "2024-02-15 10:30:00" } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `invitations` | array | Array of invitation objects | | `invitations[].id` | string | Invitation identifier | | `invitations[].inviter` | string | Name of the user who sent the invitation | | `invitations[].invitee_email` | string | Email address of the invitee | | `invitations[].entity_type` | string | Always `"org"` for org invitations | | `invitations[].state` | string | Invitation state: `"pending"`, `"accepted"`, `"declined"` | | `invitations[].created` | string | Creation timestamp | | `invitations[].expires` | string/null | Expiration timestamp | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation state was supplied." | Invalid state filter | --- ### Update an Invitation ``` POST /current/org/{org_id}/members/invitation/{invitation_id}/ ``` Auth required. Permission governed by org's `perm_member_manage` setting. `{invitation_id}` can be the invitation ID or the invitee email address. **Request parameters (all optional):** | Name | Type | Description | |------|------|-------------| | `state` | string | New invitation state | | `permissions` | string | Updated permission level | | `expires` | string (datetime) | Updated expiration date | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/members/invitation/eA1B2C3D4E5F6G7H8J9K0L1M2N3O4/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=admin" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation ID or email was supplied." | Invalid identifier | | `1605 (Invalid Input)` | 400 | "Invitation not found." | Invitation does not exist | | `1605 (Invalid Input)` | 400 | "Invitation is not for an Org" | Wrong entity type | | `1605 (Invalid Input)` | 400 | "An invalid state was supplied." | Invalid state value | | `1679 (Update Failed)` | 400 | "Failed to update invitation." | Internal error | --- ### Delete an Invitation ``` DELETE /current/org/{org_id}/members/invitation/{invitation_id}/ ``` Auth required. Permission governed by org's `perm_member_manage` setting. `{invitation_id}` can be the invitation ID or the invitee email address. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/members/invitation/eA1B2C3D4E5F6G7H8J9K0L1M2N3O4/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1666 (Delete Failed)` | 400 | "Failed to delete invitation." | Deletion failed | --- ## Organization Transfer (Agent to Human) Agents build orgs, then transfer ownership to humans. The human gets the org plus all workspaces; the agent stays as admin. **Only agent accounts can create/list/delete tokens; only human accounts can claim.** The claim URL for humans: `https://go.fast.io/claim?token={token}` ### Create Transfer Token ``` POST /current/org/{org_id}/transfer/token/create/ ``` Auth required. Agent owner only. Org must be on the agent plan. Creates a transfer token: 64-character string, valid 72 hours. Max 5 active tokens per org. **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/transfer/token/create/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "transfer_token": { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "token": "a1b2c3d4e5f6g7h8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2", "org_id": 1234567890123456789, "state": "pending", "expires": "2026-02-04T12:00:00+00:00", "created": "2026-02-01T12:00:00+00:00" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `transfer_token.id` | string | Opaque identifier of the transfer token | | `transfer_token.token` | string | 64-character token string to share with the human user | | `transfer_token.org_id` | integer | Organization being transferred | | `transfer_token.state` | string | Token state: `"pending"` | | `transfer_token.expires` | string | Expiry timestamp, 72 hours from creation (`YYYY-MM-DD HH:MM:SS`) | | `transfer_token.created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1680 (Access Denied)` | 403 | "Only agent accounts can create org transfer tokens." | Non-agent user attempted creation | | `1680 (Access Denied)` | 403 | "Transfer tokens can only be created for organizations on the agent plan." | Org not on agent plan | | `1658 (Not Acceptable)` | 406 | "Maximum of 5 active transfer tokens per organization." | Token limit reached | --- ### List Transfer Tokens ``` GET /current/org/{org_id}/transfer/token/list/ ``` Auth required. Agent owner only. Returns all active (pending) transfer tokens for the org. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/transfer/token/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "results": 1, "transfer_tokens": [ { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "token": "a1b2c3d4e5f6g7h8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2", "org_id": 1234567890123456789, "state": "pending", "expires": "2026-02-04T12:00:00+00:00", "created": "2026-02-01T12:00:00+00:00" } ] } } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1680 (Access Denied)` | 403 | "Only agent accounts can list org transfer tokens." | Non-agent user | --- ### Delete Transfer Token ``` DELETE /current/org/{org_id}/transfer/token/{token_id}/ ``` Auth required. Agent owner only. Soft-deletes a pending transfer token. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/transfer/token/eA1B2C3D4E5F6G7H8J9K0L1M2N3O4/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "transfer_token": { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "token": "a1b2c3d4e5f6g7h8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2", "org_id": 1234567890123456789, "state": "deleted", "expires": "2026-02-04T12:00:00+00:00", "created": "2026-02-01T12:00:00+00:00" } } } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1680 (Access Denied)` | 403 | "Only agent accounts can delete org transfer tokens." | Non-agent user | | `1680 (Access Denied)` | 403 | "Transfer token does not belong to this organization." | Token/org mismatch | | `1683 (Resource Missing)` | 404 | "Transfer token not found." | Invalid token ID | | `1658 (Not Acceptable)` | 406 | "Token cannot be deleted." | Token already claimed/expired/deleted | --- ### Public Token Details (Preview Before Claiming) ``` GET /current/org/transfer/claim/public/details/ ``` No auth required. IP-rate-limited. Preview transfer token details before claiming. **Query parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `token` | string | Yes | 64-character transfer token string | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/transfer/claim/public/details/?token=a1b2c3d4e5f6g7h8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "transfer_token": { "id": "eA1B2C3D4E5F6G7H8J9K0L1M2N3O4", "state": "pending", "is_claimable": true, "expires": "2026-02-04T12:00:00+00:00", "created": "2026-02-01T12:00:00+00:00" }, "org": { "id": "1234567890123456789", "domain": "my-agent-org", "name": "My Agent Org", "description": "An org built by an AI agent", "logo": null, "accent_color": null, "closed": false, "suspended": false }, "created_by": { "id": "9876543210987654321", "account_type": "agent", "email_address": "agent@example.com", "first_name": "Agent", "last_name": "Smith" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `transfer_token.id` | string | Token identifier | | `transfer_token.state` | string | `"pending"`, `"claimed"`, `"expired"`, or `"deleted"` | | `transfer_token.is_claimable` | boolean | `true` if the token is pending and not expired | | `transfer_token.expires` | string | Expiry timestamp (`YYYY-MM-DD HH:MM:SS`) | | `transfer_token.created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `org` | object/null | Organization info. `null` if the org has been deleted. | | `created_by` | object/null | Agent creator profile. `null` if the creator no longer exists. | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "A transfer token is required." | Missing `token` query parameter | | `1605 (Invalid Input)` | 400 | "Invalid transfer token format." | Token string is not exactly 64 characters | | `1683 (Resource Missing)` | 404 | "Transfer token not found or invalid." | No token matches | --- ### Claim Organization ``` POST /current/org/transfer/claim/ ``` Auth required. Human users only. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `token` | string | Yes | 64-character transfer token string | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/transfer/claim/" \ -H "Authorization: Bearer {jwt_token}" \ -d "token=a1b2c3d4e5f6g7h8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f1g2" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "org": { "id": "1234567890123456789", "domain": "my-agent-org", "plan": "free" }, "previous_owner": { "id": "9876543210987654321", "account_type": "agent" }, "workspaces_transferred": 3 } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `org.id` | string | Organization profile ID | | `org.domain` | string | Organization domain | | `org.plan` | string | New billing plan (typically `"free"`) | | `previous_owner.id` | string | Previous agent owner's user ID | | `previous_owner.account_type` | string | Always `"agent"` | | `workspaces_transferred` | integer | Number of workspaces whose membership was transferred | **What happens during claim:** 1. Organization ownership transfers to the human user 2. Previous agent owner becomes an admin member of the org 3. Billing plan changes from agent to free (credit-based, no trial period) 4. All workspaces: human becomes owner, agent becomes admin 5. Transfer token marked as `"claimed"` 6. Free trial period starts from the transfer date **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1680 (Access Denied)` | 403 | "Agent accounts cannot claim organizations." | Agent user attempted claim | | `1683 (Resource Missing)` | 404 | "Transfer token not found or invalid." | Invalid token string | | `1658 (Not Acceptable)` | 406 | "This transfer token has expired." | Token past expiry | | `1658 (Not Acceptable)` | 406 | "This transfer token has already been used." | Token not pending | | `1658 (Not Acceptable)` | 406 | "This organization is no longer eligible for transfer." | Org not on agent plan | --- ## Organization Discovery ### List Internal Orgs ``` GET /current/orgs/list/ ``` Auth required. Lists orgs where the user is a direct member (`member: true`). Non-admin/non-owner members only see orgs with active subscriptions; admins and owners always see their orgs. **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "orgs": [ { "id": "1234567890123456789", "domain": "acme-corp", "name": "Acme Corporation", "description": "Leading provider of innovation", "logo": "https://assets.fast.io/org/logo.png", "accent_color": "#0066CC", "closed": false, "suspended": false, "subscriber": true, "user_status": "joined", "member": true } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `orgs` | array | Array of organization objects | | `orgs[].id` | string | 19-digit numeric organization ID | | `orgs[].domain` | string | URL-safe subdomain | | `orgs[].name` | string/null | Display name | | `orgs[].description` | string/null | Description | | `orgs[].logo` | string/null | Logo asset URL | | `orgs[].accent_color` | string/null | Brand color | | `orgs[].closed` | boolean | Whether org is closed | | `orgs[].suspended` | boolean | Whether org is suspended | | `orgs[].subscriber` | boolean | Whether org has an active subscription | | `orgs[].user_status` | string | `"joined"` or `"available"` | | `orgs[].member` | boolean | Always `true` for this endpoint | **Subscription filtering:** | User Role | Behavior | |-----------|----------| | Owner | Always sees the org | | Admin | Always sees the org | | Member | Only sees the org if it has an active subscription | --- ### List External Orgs ``` GET /current/orgs/list/external/ ``` Auth required. Lists orgs where the user has access only through workspace membership (`member: false`). **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/list/external/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "orgs": [ { "id": "1234567890123456780", "domain": "partner-corp", "name": "Partner Corporation", "description": "External partner organization", "logo": null, "accent_color": "#FF6600", "closed": false, "suspended": false, "subscriber": true, "user_status": "available", "member": false } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `orgs` | array | Array of external organization objects | | `orgs[].user_status` | string | Always `"available"` for external orgs | | `orgs[].member` | boolean | Always `false` for this endpoint | --- ### List All Orgs ``` GET /current/orgs/all/ ``` Auth required. Lists all accessible orgs (joined + invited). **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/all/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "orgs": [ { "id": "1234567890123456789", "domain": "acme-corp", "name": "Acme Corporation", "description": "Leading provider of innovation", "logo": "https://assets.fast.io/org/logo.png", "accent_color": "#0066CC", "closed": false, "suspended": false, "user_status": "joined" } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `orgs[].user_status` | string | `"joined"` (already a member) or `"available"` (pending invitation) | --- ### List Available Orgs ``` GET /current/orgs/available/ ``` Auth required. Lists orgs available to join (not yet joined). Excludes orgs the user is already a member of. **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/available/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "orgs": [ { "id": "1234567890123456782", "domain": "new-company", "name": "New Company", "description": "An org you can join", "logo": null, "accent_color": "#FF6600", "closed": false, "suspended": false } ] } } ``` --- ### Check Domain Availability ``` GET /current/orgs/check/domain/{domain_name} ``` Auth required. Checks if an org domain name is available for use. **Path parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `{domain_name}` | string | Yes | The domain name to check for availability. | **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/check/domain/acme-corp" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted) -- domain available:** ```json { "result": "yes" } ``` **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid name was supplied." | Domain format is invalid | | `1658 (Not Acceptable)` | 406 | "The supplied name is restricted." | Domain is reserved | | `1658 (Not Acceptable)` | 406 | "The supplied name is already in use." | Domain is taken | --- ### List Industries ``` GET /current/orgs/industries/ ``` Auth required. Returns available industry types for org profiles. **curl example:** ```bash curl -X GET "https://api.fast.io/current/orgs/industries/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "technology": { "title": "Technology", "description": "Software, hardware, and IT services" }, "healthcare": { "title": "Healthcare", "description": "Medical, pharmaceutical, and health services" }, "finance": { "title": "Finance", "description": "Banking, investment, and financial services" }, "education": { "title": "Education", "description": "Schools, universities, and training providers" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `{key}` | string | Machine-readable industry identifier (use this value in create/update requests) | | `{key}.title` | string | Human-readable display name | | `{key}.description` | string | Brief description of the industry category | --- ## Billing ### Create or Update Subscription ``` POST /current/org/{org_id}/billing/ ``` Auth required. Admin or above. Creates or updates the org's billing subscription. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `billing_plan` | string | No | Target plan ID (must be a valid paid plan, e.g., `"pro_monthly"`, `"business_monthly"`). | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/billing/" \ -H "Authorization: Bearer {jwt_token}" \ -d "billing_plan=pro_monthly" ``` **Response (201 Created) -- new subscription:** ```json { "result": "yes", "response": { "subscription": { "...": "..." }, "customer": { "...": "..." }, "setup_intent": { "id": "{setup_id}", "client_secret": "{setup_id}_secret", "status": "requires_payment_method" }, "is_active": false, "is_trial_eligible": true, "public_key": "{public_key}" } } ``` **Response (202 Accepted) -- subscription updated.** **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid plan was supplied." | Plan ID not recognized | | `1605 (Invalid Input)` | 400 | "Cannot create subscription for a free plan." | Tried to subscribe to free plan | | `1658 (Not Acceptable)` | 406 | "An error occurred updating your subscription..." | Subscription update failed | | `1658 (Not Acceptable)` | 406 | "An error occurred creating the payment intent..." | Intent creation failed | --- ### Cancel Subscription ``` DELETE /current/org/{org_id}/billing/ ``` Auth required. Owner only. Cancels the org's subscription. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/org/1234567890123456789/billing/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted):** ```json { "result": "yes", "response": { "status": "cancelled", "message": "Subscription has been successfully cancelled", "closed": false } } ``` If already cancelled: ```json { "result": "yes", "response": { "status": "already_cancelled", "message": "Subscription is already cancelled" } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `status` | string | `"cancelled"` or `"already_cancelled"` | | `message` | string | Human-readable status message | | `closed` | boolean | Whether the account was also closed (plan-dependent) | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1683 (Resource Missing)` | 404 | "No subscription was found to cancel." | Org is not a subscriber | | `1654 (Internal Error)` | 500 | "Your subscription failed to be canceled..." | Cancellation failed | --- ### Get Billing Details ``` GET /current/org/{org_id}/billing/details/ ``` Auth required. Admin or above. Returns subscription/billing details. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/billing/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "subscription": { "...": "..." }, "customer": { "...": "..." }, "setup_intent": { "...": "..." }, "payment_intent": { "...": "..." }, "is_active": true, "is_trial_eligible": false, "is_cancelled": false, "current_plan": "pro_monthly", "previous_plan": null, "public_key": "{public_key}" } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `subscription` | object | Payment provider subscription details | | `customer` | object | Payment provider customer details | | `setup_intent` | object/null | Active setup intent if exists | | `payment_intent` | object/null | Active payment intent if exists | | `is_active` | boolean | Whether subscription is currently active | | `is_trial_eligible` | boolean | Whether org is eligible for a trial | | `is_cancelled` | boolean | Whether subscription is cancelled | | `current_plan` | string/null | Current plan ID | | `previous_plan` | string/null | Previous plan ID (if changed) | | `public_key` | string | Payment provider publishable key | --- ### Get Credit Usage ``` GET /current/org/{org_id}/billing/usage/limits/credits/ ``` Auth required. Admin or above. Returns credit consumption and limits. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/billing/usage/limits/credits/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "credit_limits_enabled": true, "free_org_mode": true, "org_id": "1234567890123456789", "plan": "free", "over_limit": false, "usage": { "credits_used": 1200, "credit_limit": 10000, "credits_remaining": 8800, "usage_percentage": 12.0 }, "period": { "start": "2025-01-15T10:00:00+00:00", "end": "2025-02-14T10:00:00+00:00", "days_total": 30, "days_elapsed": 10, "days_remaining": 20 }, "renewal": { "interval_days": 30, "next_renewal": "2025-02-14T10:00:00+00:00" }, "trial": null } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `credit_limits_enabled` | boolean | Whether the plan enforces credit limits | | `free_org_mode` | boolean | `true` for free orgs (reductive model) | | `over_limit` | boolean | Whether the org has exceeded its credit limit | | `usage.credits_used` | integer | Credits consumed in the current period | | `usage.credit_limit` | integer | Total credits available per period | | `usage.credits_remaining` | integer | Credits remaining in the current period | | `usage.usage_percentage` | number | Percentage of credits used | | `period.start` | string | Start of the current billing period (`YYYY-MM-DD HH:MM:SS`) | | `period.end` | string | End of the current billing period (`YYYY-MM-DD HH:MM:SS`) | | `period.days_total` | integer | Total days in the period | | `period.days_elapsed` | integer | Days elapsed since period start | | `period.days_remaining` | integer | Days remaining until renewal | | `renewal.interval_days` | integer | Days between credit renewals | | `renewal.next_renewal` | string/null | Next credit renewal timestamp (`YYYY-MM-DD HH:MM:SS`), or null | | `run_rate` | object/null | Usage rate projections (shown after 25% of period or credits used) | | `trial` | object/null | Trial info if applicable | **Credit costs:** storage (100/GB), bandwidth (212/GB), AI tokens (1/100 tokens), document ingestion (10/page), video ingestion (5/sec), image ingestion (5/image), file conversions (25/each). --- ### List Billable Members ``` GET /current/org/{org_id}/billing/usage/members/list/ ``` Auth required. Admin or above. Paginated. **Query parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `limit` | integer | 100 | 1-500 | | `offset` | integer | 0 | Items to skip | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/billing/usage/members/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "billable_members": [ { "id": "1234567890123456789", "account_type": "human", "email_address": "user@example.com", "parents": { "9876543210987654321": { "permission": "member", "date_joined": "2024-01-15 10:30:00" } } } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `billable_members` | array | Array of billable member objects | | `billable_members[].id` | string | 19-digit user ID | | `billable_members[].account_type` | string | `"human"` or `"agent"` | | `billable_members[].email_address` | string | User's email | | `billable_members[].parents` | object | Map of workspace IDs to membership details | --- ### Get Usage Meters ``` GET /current/org/{org_id}/billing/usage/meters/list/ ``` Auth required. Admin or above. Returns detailed usage breakdown by meter. **Query parameters:** | Name | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `meter` | string | Yes | -- | Meter type (e.g., `"storage_bytes"`, `"bandwidth_bytes"`, `"ai_tokens"`) | | `start_time` | string (datetime) | No | 30 days ago | Start of time range | | `end_time` | string (datetime) | No | Now | End of time range | | `workspace_id` | string | No | -- | Filter by workspace (19-digit ID) | | `share_id` | string | No | -- | Filter by share (19-digit ID) | Only one of `workspace_id` or `share_id` can be specified at a time. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/billing/usage/meters/list/?meter=storage_bytes&start_time=2024-01-01+00:00:00&end_time=2024-01-31+23:59:59" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "usage": { "meter": "storage_bytes", "total": 1073741824, "cost": 0.50, "credits": 500, "start_time": "2024-01-01 00:00:00 UTC", "end_time": "2024-01-31 23:59:59 UTC", "interval_hours": 24, "workspace_id": null, "share_id": null, "data_points": [ { "start_time": "2024-01-01 00:00:00 UTC", "end_time": "2024-01-02 00:00:00 UTC", "value": 536870912, "cost": 0.25, "credits": 250 } ] } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `usage.meter` | string | The meter type queried | | `usage.total` | number | Total usage value for the period | | `usage.cost` | number | Total cost in USD | | `usage.credits` | number/null | Total credits consumed (null for direct-billed meters) | | `usage.start_time` | string | Start of the queried range | | `usage.end_time` | string | End of the queried range | | `usage.interval_hours` | integer | Hours per data point (auto-calculated, max 30 points) | | `usage.data_points` | array | Time-series data with value, cost, and credits per interval | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Must be one of the valid meter types" | Invalid meter type | | `1605 (Invalid Input)` | 400 | "Only one of workspace_id or share_id can be specified." | Both filters provided | | `1605 (Invalid Input)` | 400 | "Start time must be before end time." | Invalid time range | | `1605 (Invalid Input)` | 400 | "Time range must be at least 1 day." | Range too short | | `1654 (Internal Error)` | 500 | "Failed to retrieve usage data." | Internal error | --- ### List Available Plans ``` GET /current/org/billing/plan/list/ ``` Auth required. Returns available billing plans. Filtered by account type: - **Agent accounts** only see the `agent` plan - **Human accounts** see human plans: free, pro, business Agents cannot subscribe to human plans. **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/billing/plan/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "results": 3, "defaults": { "pro": "pro_monthly", "business": "business_monthly" }, "plans": [ { "id": "free", "name": "Free", "category": "free", "amount": 0 }, { "id": "pro_monthly", "name": "Pro", "category": "pro", "amount": 2900 }, { "id": "business_monthly", "name": "Business", "category": "business", "amount": 4900 } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `results` | integer | Number of available plans | | `defaults` | object | Default plan IDs per category (`pro`, `business`) | | `plans` | array | Array of plan detail objects | | `plans[].id` | string | Plan identifier (use in subscription requests) | | `plans[].name` | string | Display name | | `plans[].category` | string | Plan category: `"free"`, `"pro"`, `"business"`, `"agent"` | | `plans[].amount` | integer | Price in cents (e.g., 2900 = $29.00) | --- ### List Invoices ``` GET /current/org/{org_id}/billing/invoices/ ``` Auth required. Admin or above. Returns a paginated list of invoices for the organization, including hosted URLs for linking users to their invoices. **Query parameters:** | Name | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `limit` | integer | No | 10 | Number of invoices to return (1-100) | | `starting_after` | string | No | - | Invoice ID cursor for pagination | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/billing/invoices/?limit=10" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "invoices": [ { "id": "in_1234567890", "status": "paid", "currency": "usd", "amount_due": 2900, "amount_paid": 2900, "subtotal": 2900, "total": 2900, "paid": true, "description": "Subscription creation", "hosted_invoice_url": "https://invoice.stripe.com/i/acct_xxx/...", "invoice_pdf": "https://pay.stripe.com/invoice/acct_xxx/...", "period_start": "2026-03-01 00:00:00 UTC", "period_end": "2026-04-01 00:00:00 UTC", "created": "2026-03-01 00:00:00 UTC" } ], "has_more": false } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `invoices` | array | Array of invoice objects | | `invoices[].id` | string | Invoice identifier (use as `starting_after` cursor) | | `invoices[].status` | string | `"draft"`, `"open"`, `"paid"`, `"void"`, `"uncollectible"` | | `invoices[].currency` | string | Three-letter ISO currency code (e.g., `"usd"`) | | `invoices[].amount_due` | integer | Amount due in cents | | `invoices[].amount_paid` | integer | Amount paid in cents | | `invoices[].subtotal` | integer | Subtotal before tax in cents | | `invoices[].total` | integer | Total after tax in cents | | `invoices[].paid` | boolean | Whether the invoice has been paid | | `invoices[].description` | string/null | Invoice description | | `invoices[].hosted_invoice_url` | string/null | URL to view and pay the invoice | | `invoices[].invoice_pdf` | string/null | Direct PDF download URL | | `invoices[].period_start` | string/null | Billing period start (`YYYY-MM-DD HH:MM:SS UTC`) | | `invoices[].period_end` | string/null | Billing period end (`YYYY-MM-DD HH:MM:SS UTC`) | | `invoices[].created` | string/null | Invoice creation timestamp (`YYYY-MM-DD HH:MM:SS UTC`) | | `has_more` | boolean | Whether more invoices are available | Amounts are in the smallest currency unit (cents for USD). Use `hosted_invoice_url` to link users to their invoices. --- ## Create Workspace (from Org) ``` POST /current/org/{org_id}/create/workspace/ ``` Auth required. Member or above. Creates a workspace within the org. Subject to plan feature availability and workspace creation limits. **Request parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `folder_name` | string | Yes | URL-safe folder name for the workspace. Must be globally unique across all workspaces. | | `name` | string | Yes | Display name. | | `description` | string | No | Workspace description. | | `perm_join` | string | Yes | Who can auto-join from the org. Values: `'Member or above'` (default), `'Admin or above'`, `'Only Org Owners'`. | | `perm_member_manage` | string | Yes | Who can manage workspace members. Values: `'Member or above'` (default), `'Admin or above'`. | | `intelligence` | string | Yes (non-agents) | Enable AI features (`"true"`/`"false"`). Defaults to `"true"` for agent accounts. | | `accent_color` | string (JSON) | No | Accent color as JSON. | | `background_color1` | string (JSON) | No | Primary background color as JSON. | | `background_color2` | string (JSON) | No | Secondary background color as JSON. | **curl example:** ```bash curl -X POST "https://api.fast.io/current/org/1234567890123456789/create/workspace/" \ -H "Authorization: Bearer {jwt_token}" \ -d "folder_name=project-alpha" \ -d "name=Project Alpha" \ -d "perm_join=Member or above" \ -d "perm_member_manage=Admin or above" \ -d "intelligence=true" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspace": { "id": "1234567890123456780", "folder_name": "project-alpha" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `workspace.id` | string | 19-digit numeric workspace ID | | `workspace.folder_name` | string | URL-safe folder name | **Error responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1685 (Feature Limit)` | 403 | "Workspace creation is not available on your current plan." | Feature disabled | | `1685 (Feature Limit)` | 403 | "You have reached your workspace creation limit." | Limit exceeded | | `1658 (Not Acceptable)` | 406 | "The supplied workspace folder name is already in use." | Duplicate folder name | | `1605 (Invalid Input)` | 400 | "An invalid workspace folder name was supplied." | Invalid folder name | | `1605 (Invalid Input)` | 400 | "An invalid configuration was supplied..." | Metadata validation failed | --- ## List Workspaces in Org ``` GET /current/org/{org_id}/list/workspaces/ ``` Auth required. Lists accessible workspaces within the org. **Query parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `archived` | string | `"false"` | `"true"` to show archived workspaces, `"false"` for active | **Access levels:** | Role | Access | Notes | |------|--------|-------| | Owner | Full access | Sees all workspaces | | Admin | Full access | Sees all workspaces | | Member | Filtered | Sees workspaces matching join permission level | | External | Filtered | Sees only workspaces where they are a direct member | **curl example:** ```bash curl -X GET "https://api.fast.io/current/org/1234567890123456789/list/workspaces/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspaces": [ { "id": "1234567890123456780", "folder_name": "project-alpha", "name": "Project Alpha", "description": "Main project workspace" } ] } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `workspaces` | array | Array of workspace objects | | `workspaces[].id` | string | 19-digit numeric workspace ID | | `workspaces[].folder_name` | string | URL-safe folder name | | `workspaces[].name` | string | Display name | | `workspaces[].description` | string/null | Workspace description | > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Workspace Management Base URL: `https://api.fast.io/current/` All authenticated endpoints require: `Authorization: Bearer {jwt_token}` Profile IDs are 19-digit numeric strings. Most workspace endpoints also accept the workspace's `folder_name` (e.g., `my-project`) in place of the numeric ID. --- ## Endpoint Summary ### Workspace CRUD | Method | Path | Description | |--------|------|-------------| | POST | `/current/org/{org_id}/create/workspace/` | Create a workspace | | GET | `/current/workspace/{workspace_id}/details/` | Get workspace details | | POST | `/current/workspace/{workspace_id}/update/` | Update workspace settings | | DELETE | `/current/workspace/{workspace_id}/delete/` | Delete (close) a workspace | | POST | `/current/workspace/{workspace_id}/archive/` | Archive a workspace | | POST | `/current/workspace/{workspace_id}/unarchive/` | Unarchive a workspace | ### Assets | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/assets/` | List available asset types | | GET | `/current/workspace/{workspace_id}/assets/` | List workspace assets | | POST | `/current/workspace/{workspace_id}/assets/{asset_name}/` | Upload/set asset | | DELETE | `/current/workspace/{workspace_id}/assets/{asset_name}/` | Delete asset | | GET | `/current/workspace/{workspace_id}/assets/{asset_name}/read/` | Download asset binary | | HEAD | `/current/workspace/{workspace_id}/assets/{asset_name}/read/` | Get asset metadata headers | ### Members | Method | Path | Description | |--------|------|-------------| | POST | `/current/workspace/{workspace_id}/members/{email_or_user_id}/` | Add member or send invitation | | DELETE | `/current/workspace/{workspace_id}/members/{user_id}/` | Remove a member | | GET | `/current/workspace/{workspace_id}/members/list/` | List all members | | POST | `/current/workspace/{workspace_id}/members/join/` | Self-join by org membership | | POST | `/current/workspace/{workspace_id}/members/join/{invitation_key}/{action}/` | Join via invitation | | DELETE | `/current/workspace/{workspace_id}/member/` | Leave workspace (self) | | GET | `/current/workspace/{workspace_id}/member/{member_id}/details/` | Get member details | | POST | `/current/workspace/{workspace_id}/member/{member_id}/update/` | Update member role | | GET | `/current/workspace/{workspace_id}/member/{member_id}/transfer_ownership/` | Transfer ownership | ### Invitations | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/members/invitations/list/` | List all invitations | | GET | `/current/workspace/{workspace_id}/members/invitations/list/{state}/` | List invitations by state | | POST | `/current/workspace/{workspace_id}/members/invitation/{invitation_id}/` | Update an invitation | | DELETE | `/current/workspace/{workspace_id}/members/invitation/{invitation_id}/` | Delete an invitation | ### Shares (in workspace context) | Method | Path | Description | |--------|------|-------------| | POST | `/current/workspace/{workspace_id}/create/share/` | Create a share | | GET | `/current/workspace/{workspace_id}/list/shares/` | List shares | | POST | `/current/workspace/{workspace_id}/import/share/{share_id}/` | Import a user-owned share | ### Cloud Import | Method | Path | Description | |--------|------|-------------| | POST | `/current/workspace/{workspace_id}/cloud-import/enable/` | Enable cloud import | | POST | `/current/workspace/{workspace_id}/cloud-import/disable/` | Disable cloud import | ### Workflow | Method | Path | Description | |--------|------|-------------| | POST | `/current/workspace/{workspace_id}/workflow/enable/` | Enable workflow features | | POST | `/current/workspace/{workspace_id}/workflow/disable/` | Disable workflow features | ### Task Lists (Workflow) | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/tasks/` | List task lists | | POST | `/current/workspace/{workspace_id}/tasks/create/` | Create a task list | | POST | `/current/workspace/{workspace_id}/tasks/reorder/` | Reorder task lists | ### Todos (Workflow) | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/todos/` | List todo items | | POST | `/current/workspace/{workspace_id}/todos/create/` | Create a todo item | | POST | `/current/workspace/{workspace_id}/todos/bulk-toggle/` | Bulk toggle todo done status | ### Approvals (Workflow) | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/approvals/` | List approvals | ### Discovery | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspaces/all/` | List all accessible workspaces | | GET | `/current/workspaces/available/` | List joinable workspaces | | GET | `/current/workspaces/check/name/{name}/` | Check folder name availability | | GET | `/current/org/{org_id}/list/workspaces/` | List workspaces in an org | --- ## Workspace Field Constraints | Field | Constraint | |-------|-----------| | `folder_name` | Must match regex `^[\p{L}\p{N}-]+$` (letters, numbers, hyphens). Must be globally unique. | | `name` | 2-100 characters, string | | `description` | 10-1000 characters, string (optional) | | `title` (shares) | 2-80 characters | | `custom_name` (shares) | 10-100 characters, URL-friendly | --- ## Permission Values **`perm_join`** -- who can self-join from the parent org: | Value | Description | |-------|-------------| | `'Member or above'` | Any org member can join (default) | | `'Admin or above'` | Only org admins and owners | | `'Only Org Owners'` | Only org owners | **`perm_member_manage`** -- who can manage workspace members: | Value | Description | |-------|-------------| | `'Member or above'` | Any workspace member can manage (default) | | `'Admin or above'` | Only workspace admins and owners | **Permission Levels (numeric hierarchy):** | Level | Numeric | Description | |-------|---------|-------------| | Owner | 1000 | Full control; one per workspace | | Admin | 500 | Administrative access | | Member | 100 | Standard member | | Guest | 50 | Limited guest access | | View | 20 | Read-only access | --- ## Intelligence Setting The `intelligence` boolean on a workspace controls whether uploaded files are automatically indexed for RAG (retrieval-augmented generation). - **Enable** (`intelligence=true`, default for agent accounts) -- files are auto-indexed for semantic search, summarization, and citation. Required for `chat_with_files` AI chat type. - **Disable** (`intelligence=false`) -- files are stored/shared without RAG indexing. You can still attach files directly to a `chat` type conversation for one-off analysis. - Set at creation: `POST /current/org/{org_id}/create/workspace/` with `intelligence=true|false` - Update later: `POST /current/workspace/{id}/update/` with `intelligence=true|false` - **Can be enabled and disabled within time restrictions.** Disabling intelligence destroys indexed embeddings (the vector index is flushed). Re-enabling intelligence incurs re-indexing costs as AI credits are consumed to re-index all files. --- ## Workspace CRUD ### Create Workspace ``` POST /current/org/{org_id}/create/workspace/ ``` Creates a new workspace within an organization. The authenticated user becomes the workspace owner. **Auth:** JWT required. Org membership (Member or above) required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{org_id}` | string | Yes | 19-digit numeric organization ID | **Request Parameters:** | Name | Type | Required | Constraints | Description | |------|------|----------|-------------|-------------| | `folder_name` | string | Yes | Regex `^[\p{L}\p{N}-]+$`, globally unique | URL-safe identifier used in workspace URLs | | `name` | string | Yes | 2-100 chars, non-blank | Display name | | `perm_join` | string | Yes | See Permission Values | Who can self-join from the org | | `perm_member_manage` | string | Yes | See Permission Values | Who can manage workspace members | | `intelligence` | string | Yes (auto-set for agents) | `"true"` or `"false"` | Enable AI indexing. Defaults to `"true"` for agent accounts. | | `description` | string | No | 10-1000 chars | Workspace description | | `accent_color` | string (JSON) | No | Valid JSON | Accent color styling | | `background_color1` | string (JSON) | No | Valid JSON | Background color 1 styling | | `background_color2` | string (JSON) | No | Valid JSON | Background color 2 styling | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/org/10000000000000000001/create/workspace/" \ -H "Authorization: Bearer {jwt_token}" \ -d "folder_name=engineering" \ -d "name=Engineering Team" \ -d "description=Main engineering workspace for the team" \ -d "perm_join=Member or above" \ -d "perm_member_manage=Admin or above" \ -d "intelligence=true" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspace": { "id": "12345678901234567890", "folder_name": "engineering" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.workspace.id` | string | 19-digit numeric workspace profile ID | | `response.workspace.folder_name` | string | The URL-safe folder name that was set | **Access Levels:** | Role | Access | |------|--------| | Org Owner | Can create workspaces | | Org Admin | Can create workspaces | | Org Member | Can create workspaces | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1685 (Feature Limit)` | 403 | "Workspace creation is not available on your current plan." | Feature disabled on billing plan | | `1685 (Feature Limit)` | 403 | "You have reached your workspace creation limit." | Workspace count limit exceeded | | `1658 (Not Acceptable)` | 406 | "The supplied workspace folder name is already in use." | Duplicate `folder_name` | | `1605 (Invalid Input)` | 400 | "An invalid workspace folder name was supplied." | Invalid `folder_name` format | | `1605 (Invalid Input)` | 400 | "An invalid configuration was supplied..." | Metadata validation failure | | `1663 (Update Failed)` | 500 | "There was an internal error processing your create request." | Internal failure | --- ### Get Workspace Details ``` GET /current/workspace/{workspace_id}/details/ ``` Returns full workspace details including settings, permissions, owner, intelligence state, and branding. **Auth:** JWT required. Workspace membership required (View or above). **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspace": { "id": "12345678901234567890", "name": "Engineering Team", "folder_name": "engineering", "description": "Main engineering workspace", "accent_color": "#0066CC", "logo": "https://assets.fast.io/12345678901234567890/logo.png", "closed": false, "archived": false, "perm_join": "Member or above", "perm_member_manage": "Admin or above", "created": "2023-01-15 10:30:00", "updated": "2024-01-20 14:45:00", "user_status": "joined", "org_domain": "acme-corp" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.workspace.id` | string | 19-digit workspace profile ID | | `response.workspace.name` | string | Display name | | `response.workspace.folder_name` | string | URL-safe folder identifier | | `response.workspace.description` | string or null | Workspace description | | `response.workspace.accent_color` | string or null | Brand accent color | | `response.workspace.logo` | string or null | Logo asset URL | | `response.workspace.closed` | boolean | Whether workspace is closed (soft-deleted) | | `response.workspace.archived` | boolean | Whether workspace is archived | | `response.workspace.perm_join` | string | Who can join | | `response.workspace.perm_member_manage` | string | Who can manage members | | `response.workspace.created` | string | Creation timestamp | | `response.workspace.updated` | string | Last update timestamp | | `response.workspace.user_status` | string | Current user's membership status (`"joined"`, `"invited"`, `"available"`) | | `response.workspace.org_domain` | string | Parent organization domain | **Access Levels:** | Role | Fields Returned | |------|-----------------| | Owner | All fields including intelligence, storage, platform details | | Admin | All fields including intelligence, storage, platform details | | Member | Core workspace fields | | View | Basic workspace information | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid JWT | | `1609 (Not Found)` | 404 | "Workspace not found" | Invalid ID or no access | --- ### Update Workspace ``` POST /current/workspace/{workspace_id}/update/ ``` Updates workspace configuration. All fields are optional; only provided fields are updated. **Auth:** JWT required. Admin or Owner required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID | **Request Parameters (all optional):** | Name | Type | Constraints | Description | |------|------|-------------|-------------| | `folder_name` | string | Regex `^[\p{L}\p{N}-]+$`, unique | URL-safe identifier | | `name` | string | 2-100 chars. Send `"null"` to clear. | Display name | | `description` | string | 10-1000 chars. Send `"null"` or `""` to clear. | Description | | `perm_join` | string | See Permission Values | Who can self-join | | `perm_member_manage` | string | See Permission Values | Who can manage members | | `intelligence` | string | `"true"` or `"false"`. Can be toggled. Disabling flushes embeddings; re-enabling re-indexes (costs AI credits). | AI indexing toggle | | `accent_color` | string (JSON) | Valid JSON. Send `"null"` to clear. | Accent color | | `background_color1` | string (JSON) | Valid JSON. Send `"null"` to clear. | Background color 1 | | `background_color2` | string (JSON) | Valid JSON. Send `"null"` to clear. | Background color 2 | | `owner_defined` | string (JSON) | Valid JSON. Send `"null"` or `""` to clear. | Custom owner-defined properties | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=Updated Workspace Name" \ -d "description=New description for the workspace" \ -d "perm_join=Admin or above" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | User is not admin or owner | | `1605 (Invalid Input)` | 400 | "An invalid workspace folder name was supplied." | Invalid `folder_name` | | `1658 (Not Acceptable)` | 406 | "The supplied workspace folder name is already in use." | Duplicate `folder_name` | | `1605 (Invalid Input)` | 400 | "Intelligence toggle is temporarily restricted..." | Intelligence toggle rate-limited or restricted by time window | | `1605 (Invalid Input)` | 400 | "An invalid configuration was supplied..." | Metadata validation failure | | `1663 (Update Failed)` | 500 | "There was an internal error processing your update request." | Internal failure | **Notes:** - If no fields have changed, returns `200 OK` without making changes. - JSON fields are decoded server-side; send valid JSON strings. --- ### Delete Workspace ``` DELETE /current/workspace/{workspace_id}/delete/?confirm={folder_name_or_id} ``` Permanently close (soft-delete) a workspace. Enters a retention period before final purge. **Auth:** JWT required. Owner only. 2FA required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID | **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `confirm` | string | Yes | Must match the workspace's `folder_name` (case-insensitive) or numeric `id`. Safety confirmation. | **curl Example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/delete/?confirm=engineering" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | User is not the workspace owner | | `1605 (Invalid Input)` | 400 | "The `confirm` field provided does not match." | Confirmation does not match folder name or ID | | `1663 (Update Failed)` | 500 | "There was an internal error processing your request." | Internal failure | --- ### Archive Workspace ``` POST /current/workspace/{workspace_id}/archive/ ``` Archives a workspace. Archived workspaces are hidden from default listings. **Auth:** JWT required. Admin or Owner required. 2FA required. **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/archive/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1663 (Update Failed)` | 400 | "The workspace is already archived." | Already archived | --- ### Unarchive Workspace ``` POST /current/workspace/{workspace_id}/unarchive/ ``` Restores an archived workspace to active status. **Auth:** JWT required. Admin or Owner required. 2FA required. **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/unarchive/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1663 (Update Failed)` | 400 | "The workspace is not archived. It cannot be unarchived." | Not currently archived | --- ## Workspace Assets ### List Available Asset Types ``` GET /current/workspace/assets/ ``` Returns available workspace asset metadata types (e.g., logo). **Auth:** JWT required. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/assets/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "assets": [ { "name": "logo", "type": "image", "max_size": 5242880, "accepted_formats": ["image/png", "image/jpeg", "image/svg+xml"] } ] }, "current_api_version": "1.0" } ``` --- ### List Workspace Assets ``` GET /current/workspace/{workspace_id}/assets/ ``` Returns assets currently set on the workspace. **Auth:** JWT required. Owner only. 2FA required. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/assets/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "assets": { "logo": { "url": "https://assets.fast.io/12345678901234567890/logo.png", "size": 102400, "content_type": "image/png" } } }, "current_api_version": "1.0" } ``` --- ### Upload/Set Workspace Asset ``` POST /current/workspace/{workspace_id}/assets/{asset_name}/ ``` Upload or replace an asset. Sent as multipart/form-data. **Auth:** JWT required. Admin or Owner required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID | | `{asset_name}` | string | Yes | Name of the asset (e.g., `logo`) | **Request Body (multipart/form-data):** | Field | Type | Required | Description | |-------|------|----------|-------------| | `file` | file | Yes | The asset file to upload | | `metadata` | string (JSON array) | No | Optional metadata for the asset | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/assets/logo/" \ -H "Authorization: Bearer {jwt_token}" \ -F "file=@/path/to/logo.png" \ -F "metadata={}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "asset": { "name": "logo", "url": "https://assets.fast.io/12345678901234567890/logo.png" } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1604 (File Missing)` | 400 | "Asset upload missing" | No file in request | | `1605 (Invalid Input)` | 400 | "metadata invalid" | Invalid metadata format | --- ### Delete Workspace Asset ``` DELETE /current/workspace/{workspace_id}/assets/{asset_name}/ ``` Delete a specific asset from a workspace. **Auth:** JWT required. Admin or Owner required. **curl Example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/assets/logo/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` --- ### Read Asset Binary ``` GET /current/workspace/{workspace_id}/assets/{asset_name}/read/ HEAD /current/workspace/{workspace_id}/assets/{asset_name}/read/ ``` GET returns raw binary data. HEAD returns metadata headers only (`Content-Type`, `Content-Length`). **Auth:** JWT required. Any workspace member. 2FA required. **curl Example:** ```bash # Download asset curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/assets/logo/read/" \ -H "Authorization: Bearer {jwt_token}" \ --output logo.png # Get metadata headers only curl -I "https://api.fast.io/current/workspace/12345678901234567890/assets/logo/read/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Raw binary content with appropriate `Content-Type` header (not JSON). --- ## Workspace Members ### Add or Invite a Member ``` POST /current/workspace/{workspace_id}/members/{email_or_user_id}/ ``` Add an existing user directly by user ID, or send an invitation by email address. **Auth:** JWT required. Permission depends on workspace `perm_member_manage` setting. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID | | `{email_or_user_id}` | string | Yes | 19-digit user ID (direct add) or email address (invitation) | **Request Parameters (adding by user ID):** | Name | Type | Required | Description | |------|------|----------|-------------| | `permissions` | string | No | `"admin"`, `"member"`, or `"guest"`. Cannot be `"owner"`. | | `notifications` | string | No | Notification preference | | `expires` | string | No | Membership expiration (`YYYY-MM-DD HH:MM:SS`) | | `force_notification` | boolean | No | Force notification email to existing user | **Request Parameters (inviting by email):** | Name | Type | Required | Description | |------|------|----------|-------------| | `permissions` | string | No | `"admin"`, `"member"`, or `"guest"`. Cannot be `"owner"`. | | `message` | string | No | Custom message in invitation email | | `invitation_expires` | string | No | Invitation expiration (`YYYY-MM-DD HH:MM:SS`) | **curl Examples:** ```bash # Add existing user by ID curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/98765432109876543210/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=member" # Invite by email curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/newuser@example.com/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=member" \ -d "message=Welcome to the project!" ``` **Response -- Direct Add (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Response -- Invitation Sent (200 OK):** ```json { "result": "yes", "response": { "invitation": { "id": "12345678901234567890", "inviter": "John Doe", "invitee_email": "newuser@example.com", "entity_type": "workspace", "state": "pending", "created": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Cannot Add As Owner)` | 400 | "Adding a member as an owner is not allowed" | Attempted owner-level permission | | `1656 (Limit Exceeded)` | 400 | Varies | Workspace or org member limit reached | | `1680 (Access Denied)` | 403 | "Insufficient org permissions to invite to this workspace." | Lacks org-level permission | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Below workspace `perm_member_manage` level | **Notes:** - Adding a user to a workspace may also auto-add them to the parent organization if the workspace is configured for automatic org membership. --- ### Remove a Member ``` DELETE /current/workspace/{workspace_id}/members/{user_id}/ ``` Removes a member from the workspace. Cannot remove the workspace owner. **Auth:** JWT required. Permission depends on `perm_member_manage`. **curl Example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/members/98765432109876543210/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Cannot remove the owner" | Attempted to remove workspace owner | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Below required permission level | **Notes:** - Removing a member cascades removal into all shares within the workspace. --- ### List Workspace Members ``` GET /current/workspace/{workspace_id}/members/list/ ``` Lists all members with their permissions, notification preferences, and membership metadata. **Auth:** JWT required. Any workspace member. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/members/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "users": [ { "id": "12345678901234567890", "account_type": "human", "email_address": "owner@example.com", "first_name": "Alice", "last_name": "Johnson", "permissions": "owner", "status": "active", "invite": "accepted", "notify": "Email me", "expires": null }, { "id": "98765432109876543210", "account_type": "agent", "email_address": "bot@example.com", "first_name": "Sync", "last_name": "Bot", "permissions": "member", "status": "active", "invite": "accepted", "notify": "Notify me in app", "expires": "2025-12-31 23:59:59" }, { "id": "55667788990011223344", "account_type": "human", "email_address": "invited@example.com", "first_name": "invited@example.com", "last_name": "", "permissions": "member", "status": "pending", "invite": { "id": "55667788990011223344", "created": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" }, "notify": "Notify me in app", "expires": null } ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `users` | array | Array of member objects | | `users[].id` | string | 19-digit user profile ID | | `users[].account_type` | string | `"human"` or `"agent"` | | `users[].email_address` | string | Member's email address | | `users[].first_name` | string | First name | | `users[].last_name` | string | Last name | | `users[].permissions` | string | Role: `"owner"`, `"admin"`, `"member"`, `"guest"` | | `users[].status` | string | `"active"` for registered users, `"pending"` for invited users who have not yet signed up | | `users[].invite` | string or object | `"accepted"` for active members; object with `id`, `created`, `expires` for pending members | | `users[].notify` | string | Notification preference | | `users[].expires` | string or null | Membership expiration or `null` for permanent | --- ### Leave Workspace (Self) ``` DELETE /current/workspace/{workspace_id}/member/ ``` Removes the authenticated user from the workspace. Owners cannot leave; they must transfer ownership first. **Auth:** JWT required. **curl Example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/member/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You cannot leave a workspace you are the owner of, transfer ownership or close workspace." | User is the owner | | `1605 (Invalid Input)` | 400 | "You cannot leave an workspace you are not a member of." | Not a member | --- ### Get Member Details ``` GET /current/workspace/{workspace_id}/member/{member_id}/details/ ``` Returns membership details for a specific user. **Auth:** JWT required. Any workspace member. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/member/98765432109876543210/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "member": { "id": "98765432109876543210", "account_type": "human", "email_address": "user@example.com", "first_name": "Jane", "last_name": "Smith", "permissions": "member", "invite": "accepted", "notify": "Notify me in app", "expires": null } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `member.id` | string | 19-digit user profile ID | | `member.account_type` | string | `"human"` or `"agent"` | | `member.email_address` | string | Email address | | `member.first_name` | string | First name | | `member.last_name` | string | Last name | | `member.permissions` | string | Permission level name | | `member.invite` | string | Invitation status | | `member.notify` | string | Notification preference | | `member.expires` | string or null | Membership expiration or `null` | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | User is not a member | --- ### Update a Member ``` POST /current/workspace/{workspace_id}/member/{member_id}/update/ ``` Updates a member's role, notification preferences, or expiration. **Auth:** JWT required. Permission depends on `perm_member_manage`. **Request Parameters (all optional):** | Name | Type | Description | |------|------|-------------| | `permissions` | string | New role: `"admin"`, `"member"`, `"guest"` | | `notifications` | string | Notification preference | | `expires` | string | Membership expiration (`YYYY-MM-DD HH:MM:SS`) | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/member/98765432109876543210/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=admin" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | Target is not a member | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Below required permission level | **Notes:** - Users cannot escalate permissions beyond their own level. --- ### Transfer Workspace Ownership ``` GET /current/workspace/{workspace_id}/member/{member_id}/transfer_ownership/ ``` Transfers ownership to another member. Current owner is demoted to admin. **Auth:** JWT required. Owner only. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/member/98765432109876543210/transfer_ownership/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "You cannot transfer ownership to yourself." | Target is self | | `1605 (Invalid Input)` | 400 | "The membership you specified does not exist." | Target is not a workspace member | | `1605 (Invalid Input)` | 400 | "Member is already an owner." | Target is already owner | | `1605 (Invalid Input)` | 400 | "The new owner must be a member of the parent organization." | Target not in parent org | | `1663 (Update Failed)` | 500 | "Failed to update owner of the workspace." | Internal failure | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Not the workspace owner | --- ### Join Workspace ``` POST /current/workspace/{workspace_id}/members/join/ ``` Self-join a workspace based on org membership. Subject to the workspace's `perm_join` setting. **Auth:** JWT required. **Request Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | `notifications` | string | No | Notification preference | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/join/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "You do not have the appropriate permissions to join this workspace." | User's org role does not meet `perm_join` | | `1656 (Limit Exceeded)` | 400 | Varies | Workspace member limit reached | **Notes:** - Self-joining members are assigned the `member` role. Only notification preferences are accepted. - Self-joined memberships do not expire. --- ### Join Workspace via Invitation ``` POST /current/workspace/{workspace_id}/members/join/{invitation_key}/{action}/ ``` Join or decline a workspace invitation. **Auth:** JWT required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{invitation_key}` | string | Yes | Unique invitation key | | `{action}` | string | No | `"accept"` (default) or `"decline"` | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/join/abc123def456/accept/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1654 (Internal Error)` | 500 | "Failed to get invitation." | Invalid or expired invitation key | | `1680 (Access Denied)` | 403 | "The inviter no longer has appropriate permissions..." | Inviter lost management permissions | | `1656 (Limit Exceeded)` | 400 | Varies | Member limit reached | **Notes:** - The system validates the inviter still has sufficient permissions at acceptance time. - Accepting may also auto-add the user to the parent organization. --- ### Pending Members When a user is invited to a workspace by email but does not yet have a Fast.io account, they appear as a **pending member** in the member list. Pending members are placeholders that allow teams to pre-assign workflow items before the invitee signs up. **How pending members appear in responses:** - The `status` field is `"pending"` (vs `"active"` for registered users). - An `invite` object is included with basic invitation details (`id`, `created`, `expires`). - `email_address` shows the invited email address. - `first_name` is set to the invited email address; `last_name` is empty. **Example member list entry (pending):** ```json { "id": "55667788990011223344", "account_type": "human", "email_address": "newuser@example.com", "first_name": "newuser@example.com", "last_name": "", "permissions": "member", "status": "pending", "invite": { "id": "55667788990011223344", "created": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" }, "notify": "Notify me in app", "expires": null } ``` **Workflow assignments:** Pending members can be assigned to tasks, approvals, and todos before they create an account. Their user ID is valid and works with all assignment endpoints. **Account claim:** When the invited user signs up or accepts the invitation with an existing account, their status transitions from `"pending"` to `"active"`. All existing workflow assignments (tasks, approvals, todos) are preserved and transfer automatically. **Removal:** To remove a pending member, delete their invitation using the invitation endpoints (see Workspace Invitations below). Deleting the invitation removes the pending member and unassigns any workflow items associated with them. **Notifications:** Pending members do not receive in-app or email notifications until they claim their account. --- ## Workspace Invitations ### List Workspace Invitations ``` GET /current/workspace/{workspace_id}/members/invitations/list/ ``` Returns all invitations for the workspace. **Auth:** JWT required. Any workspace member. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/members/invitations/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "invitations": [ { "id": "12345678901234567890", "inviter": "Alice Johnson", "invitee_email": "newuser@example.com", "invitee_uid": null, "accepted_uid": null, "entity_type": "workspace", "workspace": { "id": "98765432109876543210", "name": "Project Alpha" }, "state": "pending", "created": "2025-01-15 10:30:00", "updated": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" } ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `invitations` | array | Array of invitation objects | | `invitations[].id` | string | Invitation ID | | `invitations[].inviter` | string | Display name of inviting user | | `invitations[].invitee_email` | string | Email the invitation was sent to | | `invitations[].invitee_uid` | string or null | User ID of invitee (if they exist in the system) | | `invitations[].accepted_uid` | string or null | User ID of acceptor (if accepted) | | `invitations[].entity_type` | string | Always `"workspace"` | | `invitations[].workspace` | object | Workspace details (`id`, `name`) | | `invitations[].state` | string | `"pending"`, `"accepted"`, `"declined"`, `"expired"`, `"revoked"` | | `invitations[].created` | string | Creation timestamp | | `invitations[].updated` | string | Last update timestamp | | `invitations[].expires` | string or null | Expiration timestamp | --- ### List Invitations by State ``` GET /current/workspace/{workspace_id}/members/invitations/list/{state}/ ``` Filter invitations by state. **Auth:** JWT required. Any workspace member. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{state}` | string | Yes | `"pending"`, `"accepted"`, `"declined"`, `"expired"`, `"revoked"` | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/members/invitations/list/pending/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same format as List Workspace Invitations, filtered by state. **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation state was supplied." | Unrecognized state | --- ### Update an Invitation ``` POST /current/workspace/{workspace_id}/members/invitation/{invitation_id}/ ``` Update an existing invitation. The `{invitation_id}` can be the numeric ID or the invitee's email address. **Auth:** JWT required. Permission depends on `perm_member_manage`. **Request Parameters (all optional):** | Name | Type | Description | |------|------|-------------| | `state` | string | New state: `"pending"`, `"accepted"`, `"declined"`, `"expired"`, `"revoked"` | | `permissions` | string | Updated permission level for membership | | `expires` | string | Updated membership expiration (`YYYY-MM-DD HH:MM:SS`) | **curl Example:** ```bash # Revoke by ID curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/invitation/11111111111111111111/" \ -H "Authorization: Bearer {jwt_token}" \ -d "state=revoked" # Update by email curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/members/invitation/user@example.com/" \ -H "Authorization: Bearer {jwt_token}" \ -d "permissions=admin" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation ID or email was supplied." | Malformed identifier | | `1605 (Invalid Input)` | 400 | "Invitation not found." | No matching invitation | | `1605 (Invalid Input)` | 400 | "An invalid state was supplied." | Unrecognized state | | `1679 (Update Failed)` | 500 | "Failed to update invitation." | Internal failure | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Below required permission level | --- ### Delete an Invitation ``` DELETE /current/workspace/{workspace_id}/members/invitation/{invitation_id}/ ``` Delete (revoke) an invitation. The `{invitation_id}` can be the numeric ID or the invitee's email. **Auth:** JWT required. Permission depends on `perm_member_manage`. **curl Example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/members/invitation/11111111111111111111/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid invitation ID or email was supplied." | Malformed identifier | | `1605 (Invalid Input)` | 400 | "Invitation not found." | No matching invitation | | `1666 (Delete Failed)` | 500 | "Failed to delete invitation." | Internal failure | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | Below required permission level | --- ## Creating Shares from Workspaces ``` POST /current/workspace/{workspace_id}/create/share/ ``` Create a new share within a workspace. Shares can use independent storage (isolated portal) or a workspace folder as their storage root (live folder share). **Auth:** JWT required. Admin or Owner required. For full share management documentation, see `shares.txt`. **Required Parameters:** | Name | Type | Constraints | Description | |------|------|-------------|-------------| | `intelligence` | string | `"true"` or `"false"` | Enable AI features for the share | | `share_type` | string | `"send"`, `"receive"`, `"exchange"` | Type of share | | `access_options` | string | See access options below | Access control setting | | `invite` | string | `"owners"` or `"guests"` | Who can manage invitations | **Optional Parameters:** | Name | Type | Constraints | Description | |------|------|-------------|-------------| | `storage_mode` | string | `"independent"` (default) or `"workspace_folder"` | Storage isolation mode | | `folder_node_id` | string | Valid OpaqueId | Existing workspace folder (for `workspace_folder` mode) | | `create_folder` | string | `"true"` or `"false"` | Create new folder (for `workspace_folder` mode) | | `folder_name` | string | | Name for new folder (defaults to `"Shared Folder"`) | | `title` | string | 2-80 chars | Display title | | `description` | string | 10-500 chars | Share description | | `custom_name` | string | 10-100 chars, URL-friendly | Custom URL name. Auto-generated if omitted. | | `password` | string | 4-128 chars | Password protection (Send type only, requires `"Anyone with the link"` access) | | `expires` | string | datetime | Expiration date (portals only, not for workspace folder shares) | | `notify` | string | `"never"`, `"notify_on_file_received"`, `"notify_on_file_sent_or_received"` | Notification preference | | `comments_enabled` | string | `"true"` or `"false"` | Enable comments | | `download_security` | string | `high`, `medium`, `off` | Download security level. `high`: downloads disabled. `medium`: restricted. `off`: unrestricted. | | `guest_chat_enabled` | string | `"true"` or `"false"` | Enable guest AI chat | | `accent_color` | string (JSON) | Valid JSON | Accent color | | `background_color1` | string (JSON) | Valid JSON | Background color 1 | | `background_color2` | string (JSON) | Valid JSON | Background color 2 | | `owner_defined` | string (JSON) | Valid JSON | Custom properties | **Access Options (`access_options`):** | Value | Description | |-------|-------------| | `'Only members of the Share or Workspace'` | Most restrictive (default) | | `'Members of the Share, Workspace or Org'` | Includes org members | | `'Anyone with a registered account'` | Any authenticated user | | `'Anyone with the link'` | Least restrictive; allows password. Not available for Receive/Exchange types. | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/create/share/" \ -H "Authorization: Bearer {jwt_token}" \ -d "title=Client Deliverables" \ -d "share_type=send" \ -d "intelligence=true" \ -d "access_options=Anyone with a registered account" \ -d "invite=owners" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "share": { "id": "98765432109876543210", "custom_name": "abc123opaque", "storage_mode": "independent" } }, "current_api_version": "1.0" } ``` **Response -- Workspace Folder Share:** ```json { "result": "yes", "response": { "share": { "id": "98765432109876543210", "custom_name": "def456opaque", "storage_mode": "workspace_folder", "folder_node_id": "abc123def456" } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.share.id` | string | 19-digit share profile ID | | `response.share.custom_name` | string | URL name (custom or auto-generated) | | `response.share.storage_mode` | string | `"independent"` or `"workspace_folder"` | | `response.share.folder_node_id` | string | (Workspace folder shares only) Folder node ID | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1685 (Feature Limit)` | 403 | "The organization has reached its share creation limit..." | Plan share limit exceeded | | `1658 (Not Acceptable)` | 406 | "The supplied share custom name is already in use." | Duplicate `custom_name` | | `1605 (Invalid Input)` | 400 | "An invalid share custom name was supplied." | Invalid `custom_name` format | | `1605 (Invalid Input)` | 400 | "Workspace folder shares cannot have an expiration date." | `expires` set on workspace folder share | | `1605 (Invalid Input)` | 400 | "Receive and Exchange shares cannot have \"Anyone\" access option." | Invalid access/type combination | | `1605 (Invalid Input)` | 400 | "Password can only be set for shares with \"Anyone\" access option." | Password on non-public share | | `1658 (Not Acceptable)` | 406 | "This folder has already been shared." | Folder already has a share | | `1660 (Conflict)` | 409 | "Unable to process share creation request due to concurrent operation." | Concurrent folder share creation | --- ### List Shares in Workspace ``` GET /current/workspace/{workspace_id}/list/shares/ ``` Lists all shares belonging to a workspace. **Auth:** JWT required. View or above. **Query Parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `archived` | string | `"false"` | `"true"` for archived shares, `"false"` for active | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/list/shares/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "shares": [ { "id": "98765432109876543210", "title": "Client Deliverables", "share_type": "send", "custom_name": "client-deliverables", "archived": false, "closed": false } ] }, "current_api_version": "1.0" } ``` --- ### Import Share into Workspace ``` POST /current/workspace/{workspace_id}/import/share/{share_id}/ ``` Transfers a user-owned share into workspace ownership. **Auth:** JWT required. Must be workspace member AND share owner. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit workspace ID | | `{share_id}` | string | Yes | 19-digit share ID to import | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/import/share/98765432109876543210/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "share": { "id": "98765432109876543210", "parent_type": "workspace", "parent_workspace": "12345678901234567890" } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "This share is not owned by you and cannot be imported." | Share parent is not the current user | | `1650 (Authentication Invalid)` | 401 | "You must be the owner of the share to import it to a workspace." | Not the share owner | | `1605 (Invalid Input)` | 400 | "The share has multiple owners..." | Remove other owners first | | `1685 (Feature Limit)` | 403 | "The workspace has reached its share limit..." | Plan limit exceeded | **Notes:** - Share must be user-owned (not already in another workspace). - User must be the sole owner. Multiple owners must be removed first. - Archived shares are auto-unarchived during import. --- ## Workspace Discovery ### List All Workspaces ``` GET /current/workspaces/all/ ``` Lists all workspaces the user has joined or can access across all organizations. **Auth:** JWT required. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspaces/all/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspaces": [ { "id": "12345678901234567890", "name": "Engineering Team", "folder_name": "engineering", "description": "Main engineering workspace", "accent_color": "#0066CC", "logo": "https://assets.fast.io/12345678901234567890/logo.png", "closed": false, "archived": false, "perm_join": "Member or above", "perm_member_manage": "Admin or above", "created": "2023-01-15 10:30:00", "updated": "2024-01-20 14:45:00", "user_status": "joined", "org_domain": "acme-corp" } ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.workspaces` | array | Array of workspace objects | | `[].id` | string | 19-digit workspace profile ID | | `[].name` | string | Display name | | `[].folder_name` | string | URL-safe folder identifier | | `[].description` | string or null | Description | | `[].accent_color` | string or null | Brand accent color | | `[].logo` | string or null | Logo asset URL | | `[].closed` | boolean | Whether closed | | `[].archived` | boolean | Whether archived | | `[].perm_join` | string | Who can join | | `[].perm_member_manage` | string | Who can manage members | | `[].created` | string | Creation timestamp | | `[].updated` | string | Last update timestamp | | `[].user_status` | string | `"joined"`, `"invited"`, or `"available"` | | `[].org_domain` | string | Parent organization domain | **Notes:** - Spans all organizations the user belongs to. - Workspaces from orgs without active subscriptions are filtered out. --- ### List Available Workspaces ``` GET /current/workspaces/available/ ``` Lists workspaces the user can join but has not yet joined. Useful for discovery UI. **Auth:** JWT required. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspaces/available/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same structure as List All Workspaces, but only includes un-joined workspaces. --- ### Check Workspace Name ``` GET /current/workspaces/check/name/{name}/ ``` Checks if a workspace folder name is already in use. Useful for real-time form validation. **Auth:** JWT required. Org membership (Member or above) required. 2FA required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{name}` | string | Yes | The folder name to check | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspaces/check/name/engineering/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response -- Name Available (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "An invalid workspace folder name was supplied." | Invalid name format | | `1658 (Not Acceptable)` | 406 | "The supplied workspace folder name is already in use." | Name taken | | `1680 (Access Denied)` | 403 | "Permission denied" | Not an org member | **Notes:** - Checks globally across all workspaces, not just the current org. - Returns `202 Accepted` (not `200 OK`) when the name is available. --- ### List Workspaces in Org ``` GET /current/org/{org_id}/list/workspaces/ ``` Lists workspaces within a specific organization. Paginated. **Auth:** JWT required. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{org_id}` | string | Yes | 19-digit numeric organization ID | **Query Parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `limit` | integer | 100 | 1-500, items per page | | `offset` | integer | 0 | Items to skip | | `archived` | string | `"false"` | `"true"` for archived, `"false"` for active | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/org/10000000000000000001/list/workspaces/?limit=50&offset=0" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "workspaces": [ { "id": "12345678901234567890", "folder_name": "engineering", "name": "Engineering Team", "description": "Main project workspace" } ], "pagination": { "total": 5, "limit": 50, "offset": 0, "has_more": false } }, "current_api_version": "1.0" } ``` **Access Levels:** | Role | Visibility | |------|-----------| | Org Owner | All workspaces | | Org Admin | All workspaces | | Org Member | Workspaces matching `perm_join` permission | | External User | Only workspaces where they are a direct member | --- ## Cloud Import ### Enable Cloud Import ``` POST /current/workspace/{workspace_id}/cloud-import/enable/ ``` Enables cloud import features for a workspace. If already enabled, returns success with a message indicating the current state. **Auth:** JWT required. Admin or Owner required. Requires the cloud import billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/cloud-import/enable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": "Cloud import features enabled", "cloud_import": true }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1685 (Feature Limit)` | 403 | Feature not available | Cloud import not included in billing plan | | `1654 (Internal Error)` | 500 | "Failed to enable cloud import features" | Internal failure | --- ### Disable Cloud Import ``` POST /current/workspace/{workspace_id}/cloud-import/disable/ ``` Disables cloud import features for a workspace. If already disabled, returns success with a message indicating the current state. **Auth:** JWT required. Admin or Owner required. Requires the cloud import billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/cloud-import/disable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": "Cloud import features disabled", "cloud_import": false }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1685 (Feature Limit)` | 403 | Feature not available | Cloud import not included in billing plan | | `1654 (Internal Error)` | 500 | "Failed to disable cloud import features" | Internal failure | --- ## Workflow ### Enable Workflow ``` POST /current/workspace/{workspace_id}/workflow/enable/ ``` Enables workflow features (tasks, todos, approvals) for a workspace. If already enabled, returns success with a message indicating the current state. **Auth:** JWT required. Admin or Owner required. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/workflow/enable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": "Workflow features enabled", "workflow": true }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1685 (Feature Limit)` | 403 | Feature not available | Workflow not included in billing plan | | `1654 (Internal Error)` | 500 | "Failed to enable workflow features" | Internal failure | --- ### Disable Workflow ``` POST /current/workspace/{workspace_id}/workflow/disable/ ``` Disables workflow features for a workspace. If already disabled, returns success with a message indicating the current state. **Auth:** JWT required. Admin or Owner required. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/workflow/disable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": "Workflow features disabled", "workflow": false }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Permission denied" | Not admin or owner | | `1685 (Feature Limit)` | 403 | Feature not available | Workflow not included in billing plan | | `1654 (Internal Error)` | 500 | "Failed to disable workflow features" | Internal failure | --- ## Task Lists Task lists are a workflow feature. Workflow must be enabled on the workspace before using these endpoints. All task list endpoints support `Accept: text/markdown` for markdown-formatted output. ### List Task Lists ``` GET /current/workspace/{workspace_id}/tasks/ ``` Returns all task lists for the workspace. Supports pagination and markdown output. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Query Parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `limit` | integer | 100 | Items per page | | `offset` | integer | 0 | Items to skip | **Headers:** | Header | Value | Description | |--------|-------|-------------| | `Accept` | `text/markdown` | (Optional) Return markdown-formatted output instead of JSON | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/tasks/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "task_lists": [ { "id": "abc123opaque", "profile_id": "12345678901234567890", "name": "Sprint Backlog", "description": "Current sprint items", "created_by": "98765432109876543210", "properties": {}, "sort_order": 0, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-20T14:45:00+00:00", "deleted": null } ], "pagination": { "limit": 100, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `task_lists` | array | Array of task list objects | | `task_lists[].id` | string | Opaque task list ID | | `task_lists[].profile_id` | string | 19-digit workspace profile ID | | `task_lists[].name` | string | Task list name | | `task_lists[].description` | string or null | Task list description | | `task_lists[].created_by` | string | 19-digit user ID of the creator | | `task_lists[].properties` | object | Custom properties | | `task_lists[].sort_order` | integer | Sort order position | | `task_lists[].created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `task_lists[].updated` | string | Last update timestamp (`YYYY-MM-DD HH:MM:SS`) | | `task_lists[].deleted` | string or null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null | | `pagination` | object | Pagination metadata | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1654 (Internal Error)` | 500 | "Failed to retrieve task lists" | Internal failure | --- ### Create Task List ``` POST /current/workspace/{workspace_id}/tasks/create/ ``` Creates a new task list in the workspace. Request body is JSON. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Request Body (JSON):** | Name | Type | Required | Description | |------|------|----------|-------------| | `name` | string | Yes | Task list name | | `description` | string | No | Task list description | | `properties` | object | No | Custom properties | | `sort_order` | integer | No | Sort order position (default: 0) | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/tasks/create/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"name": "Sprint Backlog", "description": "Current sprint items"}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "task_list": { "id": "abc123opaque", "profile_id": "12345678901234567890", "name": "Sprint Backlog", "description": "Current sprint items", "created_by": "98765432109876543210", "properties": {}, "sort_order": 0, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1605 (Invalid Input)` | 400 | "Invalid task list name" | Name validation failed | | `1605 (Invalid Input)` | 400 | "Invalid description" | Description validation failed | | `1605 (Invalid Input)` | 400 | "Invalid JSON in request body" | Malformed JSON | | `1654 (Internal Error)` | 500 | "Failed to create task list" | Internal failure | --- ### Reorder Task Lists ``` POST /current/workspace/{workspace_id}/tasks/reorder/ ``` Bulk reorder task lists within a workspace. All referenced task list IDs must belong to this workspace. Request body is JSON. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Request Body (JSON):** | Name | Type | Required | Description | |------|------|----------|-------------| | `order` | array | Yes | Array of `{id, sort_order}` objects | | `order[].id` | string | Yes | Opaque task list ID | | `order[].sort_order` | integer | Yes | New sort order position | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/tasks/reorder/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"order": [{"id": "abc123", "sort_order": 0}, {"id": "def456", "sort_order": 1}]}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "reordered": 2, "profile_id": "12345678901234567890" }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1605 (Invalid Input)` | 400 | "order must be a non-empty array..." | Missing or empty order array | | `1605 (Invalid Input)` | 400 | "Each order entry must have id and sort_order fields" | Malformed order entry | | `1605 (Invalid Input)` | 400 | "Invalid task list ID format" | Invalid opaque ID | | `1605 (Invalid Input)` | 400 | "Task list ID does not belong to this workspace" | ID not owned by workspace | | `1654 (Internal Error)` | 500 | "Failed to reorder task lists" | Internal failure | --- ## Todos Todos are a workflow feature. Workflow must be enabled on the workspace before using these endpoints. All todo endpoints support `Accept: text/markdown` for markdown-formatted output. ### List Todos ``` GET /current/workspace/{workspace_id}/todos/ ``` Returns all todo items for the workspace. Supports pagination and markdown output. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Query Parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `limit` | integer | 100 | Items per page | | `offset` | integer | 0 | Items to skip | **Headers:** | Header | Value | Description | |--------|-------|-------------| | `Accept` | `text/markdown` | (Optional) Return markdown-formatted output instead of JSON | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/todos/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "todos": [ { "id": "abc123opaque", "profile_id": "12345678901234567890", "title": "Review pull request", "done": 0, "assignee_id": "98765432109876543210", "sort_order": 0, "created_by": "98765432109876543210", "properties": null, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-20T14:45:00+00:00", "deleted": null } ], "pagination": { "limit": 100, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `todos` | array | Array of todo item objects | | `todos[].id` | string | Opaque todo ID | | `todos[].profile_id` | string | 19-digit workspace profile ID | | `todos[].title` | string | Todo title | | `todos[].done` | integer | Completion status: `0` = not done, `1` = done | | `todos[].assignee_id` | string or null | 19-digit user ID of the assignee, or null | | `todos[].sort_order` | integer | Sort order position | | `todos[].created_by` | string or null | 19-digit user ID of the creator, or null | | `todos[].properties` | object or null | Custom properties | | `todos[].created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `todos[].updated` | string | Last update timestamp (`YYYY-MM-DD HH:MM:SS`) | | `todos[].deleted` | string or null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null | | `pagination` | object | Pagination metadata | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1654 (Internal Error)` | 500 | "Failed to retrieve todo items" | Internal failure | --- ### Create Todo ``` POST /current/workspace/{workspace_id}/todos/create/ ``` Creates a new todo item in the workspace. Request body is JSON. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Request Body (JSON):** | Name | Type | Required | Description | |------|------|----------|-------------| | `title` | string | Yes | Todo title | | `assignee_id` | string | No | 19-digit user ID to assign the todo to | | `sort_order` | integer | No | Sort order position (default: 0) | | `properties` | object | No | Custom properties | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/todos/create/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"title": "Review pull request", "assignee_id": "98765432109876543210"}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "todo": { "id": "abc123opaque", "profile_id": "12345678901234567890", "title": "Review pull request", "done": 0, "assignee_id": "98765432109876543210", "sort_order": 0, "created_by": "98765432109876543210", "properties": null, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1605 (Invalid Input)` | 400 | "Invalid title" | Title validation failed | | `1605 (Invalid Input)` | 400 | "Invalid assignee ID format" | Assignee ID is not a valid numeric ID | | `1605 (Invalid Input)` | 400 | "Invalid JSON in request body" | Malformed JSON | | `1654 (Internal Error)` | 500 | "Failed to create todo item" | Internal failure | --- ### Bulk Toggle Todos ``` POST /current/workspace/{workspace_id}/todos/bulk-toggle/ ``` Sets the done status for multiple todo items at once. Request body is JSON. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Request Body (JSON):** | Name | Type | Required | Description | |------|------|----------|-------------| | `todo_ids` | array | Yes | Non-empty array of opaque todo ID strings | | `done` | boolean | Yes | `true` to mark as done, `false` to mark as not done | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/todos/bulk-toggle/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"todo_ids": ["abc123", "def456"], "done": true}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "toggled": 2, "done": true }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1605 (Invalid Input)` | 400 | "todo_ids must be a non-empty array" | Missing or empty todo_ids | | `1605 (Invalid Input)` | 400 | "done field is required (true or false)" | Missing done field | | `1605 (Invalid Input)` | 400 | "Invalid todo ID format" | Invalid opaque ID in array | | `1654 (Internal Error)` | 500 | "Failed to bulk toggle todo items" | Internal failure | --- ## Approvals Approvals are a workflow feature. Workflow must be enabled on the workspace before using this endpoint. Supports `Accept: text/markdown` for markdown-formatted output. ### List Approvals ``` GET /current/workspace/{workspace_id}/approvals/ ``` Returns approvals for the workspace. Supports optional status filtering, pagination, and markdown output. **Auth:** JWT required. View or above. Requires the workflow billing feature. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace ID or `folder_name` | **Query Parameters:** | Name | Type | Default | Description | |------|------|---------|-------------| | `status` | string | (none) | Filter by approval status | | `limit` | integer | 100 | Items per page | | `offset` | integer | 0 | Items to skip | **Headers:** | Header | Value | Description | |--------|-------|-------------| | `Accept` | `text/markdown` | (Optional) Return markdown-formatted output instead of JSON | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/approvals/?status=pending" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "approvals": [ { "id": "abc123opaque", "entity_type": "file", "entity_id": "def456opaque", "profile_id": "12345678901234567890", "requested_by": "98765432109876543210", "description": "Please review this document", "status": "pending", "approver_id": "11111111111111111111", "resolved_by": null, "resolved_at": null, "comment": null, "deadline": "2026-02-01T00:00:00+00:00", "node_id": "ghi789opaque", "properties": {}, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00" } ], "pagination": { "limit": 100, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `approvals` | array | Array of approval objects | | `approvals[].id` | string | Opaque approval ID | | `approvals[].entity_type` | string | Type of entity being approved | | `approvals[].entity_id` | string | Opaque ID of the entity | | `approvals[].profile_id` | string | 19-digit workspace profile ID | | `approvals[].requested_by` | string | 19-digit user ID of the requester | | `approvals[].description` | string or null | Approval request description | | `approvals[].status` | string | Current status | | `approvals[].approver_id` | string or null | 19-digit user ID of the designated approver | | `approvals[].resolved_by` | string or null | 19-digit user ID of whoever resolved it | | `approvals[].resolved_at` | string or null | Resolution timestamp (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].comment` | string or null | Resolution comment | | `approvals[].deadline` | string or null | Deadline (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].node_id` | string or null | Opaque ID of the related storage node | | `approvals[].properties` | object or null | Custom properties | | `approvals[].created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].updated` | string | Last update timestamp (`YYYY-MM-DD HH:MM:SS`) | | `pagination` | object | Pagination metadata | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow not enabled on workspace | | `1605 (Invalid Input)` | 400 | "Invalid status filter" | Unrecognized status value | | `1654 (Internal Error)` | 500 | "Failed to retrieve approvals" | Internal failure | > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Storage Operations Base URL: `https://api.fast.io/current/` 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 -- 30-character alphanumeric strings displayed with hyphens (e.g., `f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4`). 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 `root` mapped to the designated folder. All operations are scoped to that subtree. --- ## 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`) | | `modified` | string | all | Last-modified timestamp (`YYYY-MM-DD HH:MM:SS`) | | `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 `recent` endpoint ignores `sort_by` and `sort_dir` (always sorted by `updated` descending). --- ## List Folder Contents ``` GET /current/workspace/{workspace_id}/storage/{parent_id}/list/ GET /current/share/{share_id}/storage/{parent_id}/list/ ``` List the contents of a folder. Uses keyset pagination (see above). **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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/root/list/?sort_by=name&sort_dir=asc&page_size=100" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "nodes": { "items": [ { "id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz123", "type": "folder", "name": "Documents", "parent": "root", "created": "2025-01-01T00:00:00Z", "modified": "2025-01-20T14:45:00Z" }, { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "type": "file", "name": "photo.jpg", "parent": "root", "size": 2048000, "mimetype": "image/jpeg", "created": "2025-01-10T09:00:00Z", "modified": "2025-01-10T09:00:00Z" } ], "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 ``` GET /current/workspace/{workspace_id}/storage/{node_id}/details/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "node": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "type": "file", "name": "document.pdf", "parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz123", "size": 5242880, "hash": "d41d8cd98f00b204e9800998ecf8427e", "hash_algo": "md5", "mimetype": "application/pdf", "mimecategory": "document", "version": "v1", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-20T14:45:00Z", "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": "98765432109876543210" } } } } ``` **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) | --- ## Add File from Upload ``` POST /current/workspace/{workspace_id}/storage/{parent_id}/addfile/ POST /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 (workspace):** ```json {"type": "upload", "upload": {"id": "{upload_id}"}} ``` **`from` format (share -- also supports hash source):** ```json {"from_type": "upload", "upload": {"upload_id": "{upload_id}"}} ``` ```json {"from_type": "hash", "hash": {"hash": "{file_hash}", "hash_type": "sha256"}} ``` **curl example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/root/addfile/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'name=document.pdf' \ -d 'from={"type":"upload","upload":{"id":"abc123opaqueid"}}' ``` **Response:** ```json { "result": "yes", "response": { "node": { "id": "f4kn6-arybw-qxes3-ey9z6-cwoc4-sglhn5", "type": "file", "name": "document.pdf", "parent": "root", "size": 5242880, "hash": "d41d8cd98f00b204e9800998ecf8427e", "hash_algo": "md5", "mimetype": "application/pdf", "mimecategory": "document", "version": "v1", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-15T10:30:00Z", "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) | **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) ``` POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/root/addlink/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'link_target_type=share' \ -d 'share=my-share-name' ``` **Response:** ```json { "result": "yes" } ``` **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 ``` POST /current/workspace/{workspace_id}/storage/{parent_id}/createfolder/ POST /current/share/{share_id}/storage/{parent_id}/createfolder/ ``` Create a new folder. **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 | **curl example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/root/createfolder/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'name=Documents' ``` **Response:** ```json { "result": "yes", "response": { "node": { "id": "d2bcd-efghi-jklmn-opqrs-tuvwx-yz1234", "type": "folder", "name": "Documents", "parent": "root", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-15T10:30:00Z", "restricted": false, "dmca": false, "locked": false } } } ``` **Error responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | Duplicate name in 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) ``` POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/root/createnote/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'name=meeting-notes.md' \ -d 'content=# Meeting Notes\n\nDiscussed project timeline.' ``` **Response:** ```json { "result": "yes", "response": { "note": { "id": "n5mno-pqrst-uvwxy-z1234-abcde-fghij6", "type": "note", "name": "meeting-notes.md", "parent": "root", "mimetype": "text/markdown", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-15T10:30:00Z" } } } ``` **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) ``` POST /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. **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 | New markdown content | At least one of `name` or `content` is required. **curl example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghij6/updatenote/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'content=# Updated Notes\n\nRevised content here.' ``` **Response:** ```json { "result": "yes", "response": { "note": { "id": "n5mno-pqrst-uvwxy-z1234-abcde-fghij6", "type": "note", "name": "meeting-notes.md", "parent": "root", "mimetype": "text/markdown", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-20T14:45:00Z" } } } ``` **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 | --- ## Read Note ``` GET /current/workspace/{workspace_id}/storage/{node_id}/readnote/ GET /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). **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:** ```bash # Workspace curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghij6/readnote/" \ -H "Authorization: Bearer {jwt_token}" # Share curl -X GET "https://api.fast.io/current/share/12345678901234567890/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghij6/readnote/" \ -H "Authorization: Bearer {jwt_token}" # Share with download token (no JWT needed) curl -X GET "https://api.fast.io/current/share/12345678901234567890/storage/n5mno-pqrst-uvwxy-z1234-abcde-fghij6/readnote/?token={download_token}" ``` **Response:** ```json { "result": "yes", "response": { "content": "# Meeting Notes\n\nDiscussed project timeline.", "note": { "id": "n5mno-pqrst-uvwxy-z1234-abcde-fghij6", "type": "note", "name": "meeting-notes.md", "parent": "root", "mimetype": "text/markdown", "version": "v1", "created": "2025-01-15T10:30:00Z", "modified": "2025-01-15T10:30:00Z" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `content` | string | Sanitized markdown content | | `note` | object | Full note resource (NodeResource format) | **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) | **Notes:** - On shares, a valid download token can be passed via the `token` query parameter to bypass JWT authentication. - Share permissions may restrict note reading to notes the user created (creator-only restrictions). --- ## Update Node ``` POST /current/workspace/{workspace_id}/storage/{node_id}/update/ POST /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):** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'name=new-document-name.pdf' ``` **curl example (replace content and update metadata):** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/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:** ```json { "result": "yes", "response": { "node": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "type": "file", "name": "new-document-name.pdf", "parent": "root", "size": 5242880, "version": "v2", "modified": "2025-01-22T11:00:00Z" } } } ``` **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 | | `1609 (Not Found)` | 404 | Node is in trash | **Notes:** - 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. --- ## Move Node ``` POST /current/workspace/{workspace_id}/storage/{node_id}/move/ POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/move/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'parent=d1abc-defgh-ijklm-nopqr-stuvw-xyz123' ``` **Response:** ```json { "result": "yes" } ``` **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)` | 403 | No move permission (share only) | **Notes:** - **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 ``` POST /current/workspace/{workspace_id}/storage/{node_id}/copy/ POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/copy/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'parent=d1abc-defgh-ijklm-nopqr-stuvw-xyz123' ``` **Response:** ```json { "result": "yes", "response": { "node": { "id": "f6qrs-tuvwx-yz123-abcde-fghij-klmno7", "type": "file", "name": "document.pdf", "parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz123" }, "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 | **Notes:** - Creates a deep copy for folders (all children are copied recursively). - 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 ``` POST /current/workspace/{workspace_id}/storage/{node_id}/transfer/ POST /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 (`mode=copy`, the default). Use `mode=move` to copy the node and then trash the source. There is no separate "move" endpoint -- use this transfer endpoint with the `mode` parameter to control the behavior. **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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/transfer/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'instance=98765432109876543210' \ -d 'parent=root' ``` **Move example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/transfer/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'instance=98765432109876543210' \ -d 'parent=root' \ -d 'mode=move' ``` **Response (copy):** ```json { "result": "yes", "response": { "node": { "id": "f7stu-vwxyz-12345-abcde-fghij-klmno8", "type": "file", "name": "document.pdf", "parent": "root" }, "job": null } } ``` **Response (move):** ```json { "result": "yes", "response": { "node": { "id": "f7stu-vwxyz-12345-abcde-fghij-klmno8", "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 | | `1680 (Access Denied)` | 403 | No write access to destination | | `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. - When `mode=move`, the source node is trashed in the source storage instance after the copy completes. The response includes `"source_trashed": true` on success. --- ## Delete Node (Move to Trash) ``` DELETE /current/workspace/{workspace_id}/storage/{node_id}/delete/ 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 example:** ```bash # Delete a specific node curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/delete/" \ -H "Authorization: Bearer {jwt_token}" # Empty the trash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/storage/trash/delete/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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) ``` DELETE /current/workspace/{workspace_id}/storage/{node_id}/purge/ DELETE /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:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/purge/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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 ``` POST /current/workspace/{workspace_id}/storage/{node_id}/restore/ POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/restore/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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 | No restore permission (share only) | --- ## List Versions ``` GET /current/workspace/{workspace_id}/storage/{node_id}/versions/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/versions/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "versions": [ { "id": "v8abc-defgh-ijklm-nopqr-stuvw-xyz901", "version_number": 2, "created": "2025-01-20T14:45:00Z", "operation": "update", "size": 5242880, "hash": "abc123def456789..." }, { "id": "v9bcd-efghi-jklmn-opqrs-tuvwx-yz0123", "version_number": 1, "created": "2025-01-15T10:30:00Z", "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`) | | `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 | --- ## Restore Version ``` POST /current/workspace/{workspace_id}/storage/{node_id}/restore-version/ POST /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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/restore-version/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'version_id=v9bcd-efghi-jklmn-opqrs-tuvwx-yz0123' ``` **Response:** ```json { "result": "yes", "response": { "new_version": { "id": "v0cde-fghij-klmno-pqrst-uvwxy-z12345", "version_number": 3, "created": "2025-01-25T09:00:00Z", "operation": "restore" }, "node": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "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) ``` GET /current/workspace/{workspace_id}/storage/{node_id}/read/ GET /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:** ```bash # Download with JWT auth curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/read/" \ -H "Authorization: Bearer {jwt_token}" \ -o output.pdf # Download with token (no JWT needed) curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/read/?token={download_token}" \ -o output.pdf # Download specific version curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/read/?version_id=v9bcd-efghi-jklmn-opqrs-tuvwx-yz0123" \ -H "Authorization: Bearer {jwt_token}" \ -o output_v1.pdf # Byte-range request (streaming) curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/read/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Range: bytes=0-1023" ``` **Response:** Binary file content streamed directly. - Status `200 OK` for full file, `206 Partial Content` for 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 ``` GET /current/workspace/{workspace_id}/storage/{node_id}/requestread/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/requestread/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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 ``` GET /current/workspace/{workspace_id}/storage/{folder_id}/zip/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/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 ``` GET /current/workspace/{workspace_id}/storage/recent/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/recent/?type=file&page_size=250" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "nodes": { "count": 3, "items": [ { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "name": "report.pdf", "type": "file", "parent": "d1abc-defgh-ijklm-nopqr-stuvw-xyz123", "size": 2048576, "updated": "2025-02-18T14:30:00Z", "created": "2025-02-17T10:00:00Z" } ] }, "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 DESC` and is not configurable. - Uses cursor-based (keyset) pagination, same as the `list` endpoint. - For workspace folder shares, results are post-filtered to the share's subtree. --- ## Search ``` GET /current/workspace/{workspace_id}/storage/search/ GET /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 | **curl example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/search/?search=quarterly+report" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "files": { "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4": { "name": "Q4 Report.pdf", "parent_id": "d1abc-defgh-ijklm-nopqr-stuvw-xyz123", "type": "file" }, "f6qrs-tuvwx-yz123-abcde-fghij-klmno7": { "name": "Quarterly Summary.docx", "parent_id": "root", "type": "file" } } } } ``` **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 | **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. --- ## QuickShare (Workspace Only) ``` POST /current/workspace/{workspace_id}/storage/{node_id}/quickshare/ GET /current/workspace/{workspace_id}/storage/{node_id}/quickshare/ DELETE /current/workspace/{workspace_id}/storage/{node_id}/quickshare/ ``` Create, retrieve, or delete a temporary public link for a single file. **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 **Request body (form-encoded):** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `expires` | string (digits) | No | 10800 (3 hours) | Expiration in seconds from now (max 604800 = 7 days) | | `expires_at` | string | No | - | Absolute expiration datetime. Accepts ISO 8601 (e.g., `2026-03-12T14:30:00Z`) or `YYYY-MM-DD HH:MM:SS` format. Takes precedence over `expires`. Must be in the future and within 7 days. | **curl example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/quickshare/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'expires=604800' ``` **Response:** ```json { "result": "yes", "response": { "quickshare": { "id": "qs_abc123def456", "node": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "type": "file", "name": "presentation.pdf", "size": 5242880, "mimetype": "application/pdf" }, "creator_uid": { "id": "12345678901234567890", "email_address": "john@example.com", "first_name": "John", "last_name": "Doe" }, "limit_exceeded": false, "expires": "2025-01-22 10:30:00", "created": "2025-01-15 10:30:00" } } } ``` **Error responses (POST):** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1680 (Access Denied)` | 403 | File too large for quickshare | | `1605 (Invalid Input)` | 400 | Expiration too far in the future | ### GET -- Get QuickShare Details Returns the same format as POST. Returns `1609 (Not Found)` if no quickshare exists. ### DELETE -- Delete QuickShare Returns `{"result": "yes"}`. Returns `1609 (Not Found)` 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/readnote/ -- read note content as JSON 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 ``` GET /current/workspace/{workspace_id}/storage/quickshares/list/ ``` **Auth required.** Permission: Member. Returns an array of all active quickshares in the workspace. **curl example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/quickshares/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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", "created": "2025-01-15 10:30:00" } ] } } ``` --- ## 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 ``` POST /current/workspace/{workspace_id}/storage/{node_id}/lock/ POST /current/share/{share_id}/storage/{node_id}/lock/ ``` **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:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/lock/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'duration=300' \ -d 'client_info={"device_name":"My Laptop","client_version":"2.1.0"}' ``` **Response:** ```json { "result": "yes", "response": { "lock_token": "unique_lock_token_string", "locked_at": "2025-01-28T10:00:00+00:00", "expires_at": "2025-01-28T10:05:00+00:00", "node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4" } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `lock_token` | string | Token required for heartbeat and release operations | | `locked_at` | string | Lock acquisition time, ISO 8601 (`YYYY-MM-DDTHH:MM:SS+00:00`) | | `expires_at` | string | Lock expiration time, ISO 8601 (`YYYY-MM-DDTHH:MM:SS+00:00`) | | `node_id` | string | OpaqueId of the locked node | **Error responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1609 (Not Found)` | 404 | Node does not exist | | `1609 (Not Found)` | 404 | Cannot lock a deleted node | | `1660 (Conflict)` | 409 | Node already locked by another user | ### Heartbeat (Extend Lock) ``` POST /current/workspace/{workspace_id}/storage/{node_id}/lock/heartbeat/ POST /current/share/{share_id}/storage/{node_id}/lock/heartbeat/ ``` **Auth required.** **Request body (form-encoded):** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `lock_token` | string | Yes | Token from acquire response | **curl example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/lock/heartbeat/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'lock_token=unique_lock_token_string' ``` **Response:** ```json { "result": "yes", "response": { "expires_at": "2025-01-28T10:10:00+00:00", "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 | | `1680 (Access Denied)` | 403 | Lock token does not match | **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 ``` DELETE /current/workspace/{workspace_id}/storage/{node_id}/lock/ DELETE /current/share/{share_id}/storage/{node_id}/lock/ ``` **Auth required.** **Request body/query:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `lock_token` | string | Yes | Token from acquire response | **Response:** ```json { "result": "yes", "response": { "released": true } } ``` **Error responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1609 (Not Found)` | 404 | No lock exists on this node | | `1680 (Access Denied)` | 403 | Lock token does not match | ### Lock Status ``` GET /current/workspace/{workspace_id}/storage/{node_id}/lock/ GET /current/share/{share_id}/storage/{node_id}/lock/ ``` **Auth required.** **Response (locked):** ```json { "result": "yes", "response": { "locked": true, "locked_at": "2025-01-28T10:00:00+00:00", "expires_at": "2025-01-28T10:05:00+00:00", "node_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "time_remaining": 245, "locker_uid": "12345678901234567890" } } ``` **Response (unlocked):** ```json { "result": "yes", "response": { "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 | | `hlsstream` | HLS video/audio stream | | `pdf` | PDF document preview | | `spreadsheet` | Spreadsheet 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 ``` GET /current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/preauthorize/ GET /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`, `hlsstream`, `pdf`, `spreadsheet` | **curl example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/preview/thumbnail/preauthorize/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "downloadToken": "eyJhbGciOiJIUzI1NiJ9...", "path": "/current/workspace/12345678901234567890/storage/f3jm5-zqzfx.../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 | Preview not available | ### Read Preview ``` GET /current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/read/ GET /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. **Auth required.** ### Token-Based Preview Read ``` GET /current/workspace/{workspace_id}/storage/{node_id}/preview/{preview_type}/read/{token}/file/{filename} GET /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 ``` GET /current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/transform/image/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "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 ``` POST /current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/request/ POST /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:** ```json { "result": "yes", "response": { "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 ``` GET /current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/read/ GET /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:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4/transform/image/read/?width=200&height=200&output-format=jpg" \ -H "Authorization: Bearer {jwt_token}" \ -o thumbnail.jpg ``` ### Request Transform Download Token (Workspace Only) ``` GET /current/workspace/{workspace_id}/storage/{node_id}/transform/{transform_name}/requestread/ ``` Get a temporary download token for the transformed file. **Auth required.** **Response:** ```json { "result": "yes", "response": { "token": "eyJhbGciOiJIUzI1NiJ9..." } } ``` --- ## Download Tokens Pattern The `requestread` endpoint generates temporary, auth-free download tokens for files, previews, and transforms. **Flow:** 1. `GET .../storage/{node_id}/requestread/` -- returns `{"token": "..."}` 2. `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. --- ## Workspace-Only Features These endpoints are only available on workspaces, not shares: - `addlink` -- add a share link to storage - `createnote` / `updatenote` -- markdown note creation and editing - `readnote` is available on both workspaces and shares (shares support token-based access) - `quickshare` -- temporary public file links - `quickshares/list` -- list all quickshares - `transform/.../requestread/` -- transform download tokens ## 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) - 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 `root` to 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 > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Share Management Base URL: `https://api.fast.io/current/` Shares are purpose-built portals for exchanging files with internal teams and external guests. They support branded file preview, download controls, guest access, password protection, expiration, AI-powered features, and real-time collaboration. --- ## Share Types | Type | Direction | Description | Guest Uploads | "Anyone with the link" Access | |------------|---------------|----------------------------------------------------|---------------|-------------------------------| | `send` | Owner -> Guest | Owner distributes files; guests can download only | No | Allowed | | `receive` | Guest -> Owner | Owner collects files; guests can upload only | Yes | Not allowed | | `exchange` | Bidirectional | Both parties can upload and download | Yes | Not allowed | Default share type is `exchange`. --- ## Storage Modes (Immutable at Creation) ### Portal (`storage_mode=independent`, default) The share has its own isolated storage. Files added to the portal are independent of any workspace. Changes to workspace files do not affect the portal, and vice versa. **Features:** Expiration dates, archiving, password protection (Send type only), custom branding, guest access, inline file preview, download controls, post-download messaging, AI auto-titling. ### Shared Folder (`storage_mode=workspace_folder`) The share is backed by a specific workspace folder. The share displays the live contents of that folder -- files added, updated, or removed in the workspace folder are immediately reflected in the share. No file duplication, so no extra storage cost. **Creation:** Pass `folder_node_id={folder_opaque_id}` to link an existing folder, or `create_folder=true` with `folder_name` to create a new one. **Restrictions:** Expiration dates and archiving are not allowed since the content is live. Each workspace folder can only be shared once. If the backing folder is deleted, the share becomes orphaned (`is_orphaned: true` in details response). Both modes look the same to share recipients -- a branded portal with file preview, download controls, and all share features. ### Response Field: `share_category` API responses include a `share_category` field alongside `storage_mode`. The `storage_mode` field is the input parameter used when creating shares; `share_category` is a response-only field that provides an alternate label. | `storage_mode` | `share_category` | |--------------------|-------------------| | `independent` | `portal` | | `workspace_folder` | `shared_folder` | --- ## Access Options The `access_options` parameter controls who can access the share: | Value | Description | |-------------------------------------------------|---------------------------------------------------------------------| | `'Only members of the Share or Workspace'` | Most restrictive (default). Only explicit members or workspace members. | | `'Members of the Share, Workspace or Org'` | Org members can also access | | `'Anyone with a registered account'` | Any authenticated Fast.io user | | `'Anyone with the link'` | Public access. Enables password protection (Send type only). | **Restrictions:** - Receive and Exchange shares cannot use `'Anyone with the link'` unless `anonymous_uploads_enabled` is true (requires premium plan). Without anonymous uploads, file uploads require user identity. - Enabling comments or certain notification settings on a share with `'Anyone with the link'` access automatically upgrades access to `'Anyone with a registered account'` because those features require user identity. - Changing share type to `receive` or `exchange` while access is `'Anyone with the link'` automatically upgrades access to `'Anyone with a registered account'` (unless `anonymous_uploads_enabled` is true). - Changing access away from `'Anyone with the link'` or changing type away from Receive/Exchange automatically disables `anonymous_uploads_enabled`. --- ## Create Share ``` POST /current/workspace/{workspace_id}/create/share/ ``` Create a new share in a workspace. **Auth required.** ### Path Parameters | Parameter | Type | Required | Description | |------------------|--------|----------|---------------------------------------| | `{workspace_id}` | string | Yes | 19-digit numeric workspace profile ID | ### Request Body **Required parameters:** | Parameter | Type | Required | Description | |----------------|--------|----------|-------------------------------------------------------------------| | `intelligence` | string | Yes | Boolean string (`"true"` / `"false"`). Enable AI indexing. | **Optional parameters:** | Parameter | Type | Required | Default | Constraints | Description | |----------------------|---------|----------|-----------------|----------------------------------|-----------------------------------------------------------------------------| | `share_type` | string | No | `exchange` | `send`, `receive`, `exchange` | Share direction type | | `access_options` | string | No | `Only members of the Share or Workspace` | See Access Options | Who can access the share | | `invite` | string | No | `owners` | `owners`, `guests` | Who can manage share invitations | | `storage_mode` | string | No | `independent` | `independent`, `workspace_folder`| Storage mode (immutable after creation) | | `folder_node_id` | string | No | - | Valid opaque ID | Workspace folder opaque ID (required for `workspace_folder` if not creating)| | `create_folder` | string | No | - | Boolean string | Create a new backing folder (with `folder_name`) | | `folder_name` | string | No | `Shared Folder` | Not blank | Name for the new backing folder (with `create_folder=true`) | | `title` | string | No | - | 2-80 chars | Share display title | | `description` | string | No | - | 10-500 chars | Share description | | `custom_name` | string | No | Auto-generated | 10-100 chars, URL-friendly | URL-friendly custom name. Auto-generated opaque ID if omitted. | | `password` | string | No | - | 4-128 chars | Password. Only with Send type + `'Anyone with the link'` access. | | `expires` | string | No | - | datetime (`YYYY-MM-DD HH:MM:SS`) | Expiration date. Room mode only, not allowed on shared_folder. | | `notify` | string | No | `never` | `never`, `notify_on_file_received`, `notify_on_file_sent_or_received` | Notification preference | | `comments_enabled` | string | No | - | Boolean string | Enable comments | | `download_security` | string | No | - | `high`, `medium`, `off` | Download security level. `high`: downloads disabled. `medium`: downloads require a short-lived nonce (see storage docs). `off`: downloads unrestricted. | | `guest_chat_enabled` | string | No | - | Boolean string | Enable guest AI chat | | `display_type` | string | No | - | Not blank | Visual display mode (`grid`, `list`) | | `workspace_style` | string | No | - | Not blank | Workspace visual style | | `accent_color` | string | No | - | Valid JSON | JSON color object for accent | | `background_color1` | string | No | - | Valid JSON | JSON color object for primary background | | `background_color2` | string | No | - | Valid JSON | JSON color object for secondary background | | `background_image` | integer | No | - | Numeric, validated range | Background image selection | | `link_1` | string | No | - | Valid JSON | JSON link object (custom link #1) | | `link_2` | string | No | - | Valid JSON | JSON link object (custom link #2) | | `link_3` | string | No | - | Valid JSON | JSON link object (custom link #3) | | `owner_defined` | string | No | - | Valid JSON or `"null"` | Custom owner-defined properties | | `anonymous_uploads_enabled` | string | No | `false` | Boolean string | Enable anonymous file uploads. Receive/Exchange + `'Anyone with the link'` + premium plan only. | ### Request Example ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/create/share/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "share_type=send&intelligence=true&access_options=Anyone+with+the+link&title=Client+Deliverables" ``` ### Response **Status:** `200 OK` ```json { "result": "yes", "response": { "share": { "id": "98765432109876543210", "custom_name": "aB3cD4eF5g", "storage_mode": "independent", "share_category": "portal" } }, "current_api_version": "1.0" } ``` For workspace folder shares, the response also includes `folder_node_id`. ### Response Fields | Field | Type | Description | |---------------------------|--------|-------------------------------------------------------| | `response.share.id` | string | 19-digit share profile ID | | `response.share.custom_name` | string | URL name (custom or auto-generated) | | `response.share.storage_mode` | string | `independent` or `workspace_folder` | | `response.share.share_category` | string | `portal` (independent) or `shared_folder` (workspace_folder). Response-only. | | `response.share.folder_node_id` | string | Backing folder node ID (workspace_folder only) | ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|-----------------------------------------------------------------------------|-------------------------------------------------------------| | 400 | `1605 (Invalid Input)`| "An invalid share custom name was supplied." | Custom name fails validation | | 400 | `1605 (Invalid Input)`| "An invalid share expiration date was supplied." | Invalid datetime format | | 400 | `1605 (Invalid Input)`| "Password can only be set for shares with 'Anyone' access option." | Password on non-public share | | 400 | `1605 (Invalid Input)`| "Receive and Exchange shares cannot have 'Anyone' access option..." | Public access on receive/exchange | | 400 | `1605 (Invalid Input)`| "Workspace folder shares cannot have an expiration date..." | Expiration on workspace_folder share | | 400 | `1605 (Invalid Input)`| "Must provide folder_node_id or set create_folder=true..." | Missing folder config for workspace_folder mode | | 403 | `1685 (Feature Limit)` | "The organization has reached its share creation limit..." | Share quota exceeded for billing plan | | 406 | `1658 (Not Acceptable)` | "The supplied share custom name is already in use." | Duplicate custom name | | 406 | `1658 (Not Acceptable)` | "This folder has already been shared." | Folder already linked to another share | | 409 | `1660 (Conflict)` | "Unable to process share creation request due to concurrent operation." | Concurrent operation conflict | | 500 | `1663 (Update Failed)` | "There was an internal error processing your create request." | Internal error | --- ## Share Details ``` GET /current/share/{share_id}/details/ ``` Get full share details. **Auth required (optional for public shares).** `{share_id}` accepts a 19-digit numeric ID or a custom name. ### Path Parameters | Parameter | Type | Required | Description | |--------------|--------|----------|--------------------------------------| | `{share_id}` | string | Yes | 19-digit share profile ID or custom name | ### Request Example ```bash curl -X GET "https://api.fast.io/current/share/12345678901234567890/details/" \ -H "Authorization: Bearer {jwt_token}" ``` ### Response **Status:** `200 OK` ```json { "result": "yes", "response": { "share": { "id": "12345678901234567890", "title": "Q4 Financial Reports", "description": "Quarterly financial documents", "share_type": "exchange", "custom_name": "q4-reports", "storage_mode": "independent", "share_category": "portal", "closed": false, "archived": false, "share_level": "owner", "download_security": "off", "activity_tracking": { "enabled": true, "owner_activity": true, "guest_activity": true, "own_activity": true, "all_upload_activity": false }, "comments": { "enabled": true, "owner_comments_visible": true, "guest_comments_visible": true, "personal_replies_visible": true, "owner_replies_visible": true }, "filesystem": { "file_creation": true, "file_modification": "owned", "file_download": "all", "file_view": "all", "folder_creation": true, "folder_modification": "owned" }, "event_flow": { "enabled": true, "can_see_own_events": true, "can_see_owner_events": true, "can_see_guest_events": false }, "multiplayer": { "enabled_for_user": true, "enabled_for_owners": true, "enabled_for_guests": true, "owners_can_see_guests": true, "guests_can_see_owners": false }, "member_visibility": { "user_can_see_members": true, "owners_can_see_members": true, "guests_can_see_members": false }, "invite": { "setting": "owners_only", "can_invite": true }, "capabilities": { "can_archive": true, "can_set_expiration": true }, "parent_type": "workspace", "parent_workspace": "98765432109876543210", "parent_org": "11223344556677889900", "created": "2024-01-01 10:00:00", "expires": "2024-12-31 23:59:59" } }, "current_api_version": "1.0" } ``` ### Response Fields | Field | Type | Description | |------------------------------------|--------------|--------------------------------------------------------------| | `response.share.id` | string | Share profile ID | | `response.share.title` | string\|null | Display title | | `response.share.description` | string\|null | Share description | | `response.share.share_type` | string | `send`, `receive`, or `exchange` | | `response.share.custom_name` | string\|null | URL-friendly custom name | | `response.share.storage_mode` | string | `independent` or `workspace_folder` | | `response.share.share_category` | string | `portal` (independent) or `shared_folder` (workspace_folder). Response-only. | | `response.share.closed` | boolean | Whether the share has been soft-deleted | | `response.share.archived` | boolean | Whether the share is archived | | `response.share.share_level` | string | Current user's access level: `owner`, `guest`, `public`, `excluded` | | `response.share.download_security`| string | Download security level: `high`, `medium`, or `off` | | `response.share.activity_tracking`| object | Activity visibility settings | | `response.share.comments` | object | Comment visibility and permission settings | | `response.share.filesystem` | object | File and folder operation permissions | | `response.share.event_flow` | object | Real-time event visibility settings | | `response.share.multiplayer` | object | Real-time collaboration/presence settings. Automatically determined based on share configuration -- not directly configurable via API. | | `response.share.member_visibility`| object | Who can see other share members | | `response.share.invite` | object | Invitation policy settings | | `response.share.capabilities` | object | Operations allowed based on storage mode | | `response.share.parent_type` | string\|null | `workspace` or `user` | | `response.share.parent_workspace` | string\|null | Parent workspace ID (workspace-owned shares only) | | `response.share.parent_org` | string\|null | Parent organization ID (workspace-owned shares only) | | `response.share.created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.share.expires` | string\|null | Expiration timestamp, or null | | `response.share.folder_node_id` | string\|null | Backing folder node ID (workspace_folder only) | | `response.share.is_orphaned` | boolean | Whether backing folder was deleted (workspace_folder only) | ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|-------------------|-------------------------------------------------|------------------------------------| | 401 | `1650 (Authentication Invalid)`| "Authentication required" | Non-public share without auth | | 403 | 144499 | "You do not have permissions to view this share."| Lacks view permission | --- ## Public Share Details ``` GET /current/share/{share_id}/public/details/ POST /current/share/{share_id}/public/details/ ``` Returns a comprehensive view of a share from a single API call, designed for public share landing pages. Includes share details, owner information, file listing, member list, and comments. **No auth required (optional; enhances permissions if provided).** For password-protected shares, pass the password JWT in the `x-ve-password` header (obtained from the password auth endpoint). ### Request Example ```bash # Public access (no auth) curl -X GET "https://api.fast.io/current/share/12345678901234567890/public/details/" # Password-protected share curl -X GET "https://api.fast.io/current/share/12345678901234567890/public/details/" \ -H "x-ve-password: {password_jwt_token}" # Authenticated user curl -X GET "https://api.fast.io/current/share/12345678901234567890/public/details/" \ -H "Authorization: Bearer {jwt_token}" ``` ### Response **Status:** `200 OK` ```json { "result": "yes", "response": { "share": { "id": "12345678901234567890", "title": "Q4 Financial Reports", "share_type": "exchange", "storage_mode": "independent", "share_category": "portal" }, "owner": { "id": "99887766554433221100", "display_name": "Jane Smith", "avatar": "https://..." }, "nodes": [ { "id": "aB3cD4eF5g", "name": "report.pdf", "type": "file", "size": 1048576, "parent": "root" } ], "users": [ { "id": "11223344556677889900", "display_name": "John Doe", "role": "guest" } ], "comments": [], "org": { "id": "55667788990011223344", "name": "Acme Corp" } }, "current_api_version": "1.0" } ``` ### Response Fields | Field | Type | Description | |---------------------|-------------|----------------------------------------------------------------------------| | `response.share` | object | Full share details (same as details endpoint) | | `response.owner` | object | Share owner's user resource | | `response.nodes` | array | Top-level files and folders in the share root | | `response.users` | array | Share members (if user has member visibility permission) | | `response.comments` | array | Comments on the share (filtered by visibility permissions) | | `response.org` | object\|null| Organization resource (only for workspace-owned shares) | ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|-------------------|-------------------------------------------------|------------------------------------| | 403 | 183836 | "You do not have permissions to view this share."| Lacks public view permission | | 500 | 121168 | "Failed to access share storage" | Internal storage error | --- ## Update Share ``` POST /current/share/{share_id}/update/ ``` Update share settings. **Auth required. Owner/admin only.** Supports partial updates -- only provided fields are modified. Works on archived shares. ### Request Body All fields are optional: | Parameter | Type | Constraints | Description | |------------------------|---------|-----------------------------------|---------------------------------------------------------------------------| | `name` | string | Not blank | Share display name | | `title` | string | Not blank, or `"null"` to clear | Display title (2-80 chars) | | `description` | string | `"null"` or `""` to clear | Share description (10-500 chars) | | `custom_name` | string | 10-100 chars, or `"null"` to clear| URL-friendly custom name. Must be unique. | | `share_type` | string | `send`, `receive`, `exchange` | Share direction type | | `access_options` | string | See Access Options | Who can access the share | | `invite` | string | `owners`, `guests` | Who can manage invitations | | `password` | string | 4-128 chars, `"null"`/`""` to clear | Password. Only with Send type + `'Anyone with the link'`. | | `expires` | string | datetime, `"null"` to clear | Expiration. Room mode only. | | `notify` | string | See notification values | Notification preference | | `comments_enabled` | string | Boolean string | Enable/disable comments | | `download_security` | string | `high`, `medium`, `off` | Download security level. `high`: downloads disabled. `medium`: downloads require a short-lived nonce. `off`: downloads unrestricted. | | `display_type` | string | `grid`, `list` | Visual display mode | | `workspace_style` | string | Not blank | Workspace visual style | | `guest_chat_enabled` | string | Boolean string | Enable/disable guest AI chat | | `intelligence` | string | Boolean string | Can be toggled. Disabling flushes embeddings; re-enabling re-indexes (costs AI credits). | | `accent_color` | string | JSON or `"null"` | JSON color object for accent | | `background_color1` | string | JSON or `"null"` | JSON color object for primary background | | `background_color2` | string | JSON or `"null"` | JSON color object for secondary background | | `background_image` | integer | Numeric, validated range | Background image selection | | `link_1` | string | JSON or `"null"` | JSON link object (custom link #1) | | `link_2` | string | JSON or `"null"` | JSON link object (custom link #2) | | `link_3` | string | JSON or `"null"` | JSON link object (custom link #3) | | `owner_defined` | string | JSON or `"null"` | Custom owner-defined properties | | `share_link_node_id` | string | `"null"` only | Can only be set to `"null"` to remove workspace share link node | | `anonymous_uploads_enabled` | string | Boolean string | Enable/disable anonymous file uploads. Receive/Exchange + `'Anyone with the link'` + premium plan only. Automatically disabled when access changes from "Anyone" or type changes from Receive/Exchange. | ### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/update/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "title=Updated+Title&description=New+description&share_type=exchange" ``` ### Response **Status:** `200 OK` ```json { "result": "yes", "current_api_version": "1.0" } ``` ### Important Behaviors - **Password auto-clear**: Changing access away from `'Anyone with the link'` automatically clears the password. - **Access auto-upgrade**: Enabling comments or notification settings that require user identity automatically upgrades access from `'Anyone with the link'` to `'Anyone with a registered account'`. - **Share type auto-upgrade**: Changing to `receive` or `exchange` while access is `'Anyone with the link'` upgrades access to `'Anyone with a registered account'`. - **Intelligence**: Can be enabled and disabled. Disabling destroys indexed embeddings (vector index is flushed). Re-enabling incurs re-indexing costs (AI credits consumed to re-index all files). - **Expiration**: Workspace folder shares cannot have expiration dates. Expiration is validated against the billing plan. - **Share link sync**: Updating the share name automatically renames any associated workspace share link node. ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|-----------------------------------------------------------------------------|------------------------------------------------| | 400 | `1605 (Invalid Input)`| "An invalid share custom name was supplied." | Invalid or duplicate custom name | | 400 | `1605 (Invalid Input)`| "An invalid share expiration date was supplied." | Invalid datetime format | | 400 | `1605 (Invalid Input)`| "Password can only be set for shares with 'Anyone' access option." | Password on non-public share | | 400 | `1605 (Invalid Input)`| "Intelligence toggle is temporarily restricted..." | Intelligence toggle rate-limited or restricted | | 400 | `1605 (Invalid Input)`| "Workspace folder shares cannot have an expiration date..." | Expiration on workspace_folder share | | 400 | `1605 (Invalid Input)`| "Receive and Exchange shares cannot have 'Anyone' access option..." | Public access on receive/exchange | | 403 | 144499 | "You do not have permissions to access this share." | User lacks admin permission | | 406 | `1658 (Not Acceptable)` | "The supplied share custom name is already in use." | Duplicate custom name | | 500 | `1663 (Update Failed)` | "There was an internal error processing your update request." | Internal error | --- ## Delete Share ``` DELETE /current/share/{share_id}/delete/ ``` Soft-delete (close) a share. **Auth required. Owner/admin only.** Requires confirmation. ### Request Body | Parameter | Type | Required | Description | |-----------|--------|----------|----------------------------------------------------------------------------------| | `confirm` | string | Yes | Must match either the share's `custom_name` or the share's `id`. Safety check. | ### Request Example ```bash curl -X DELETE "https://api.fast.io/current/share/12345678901234567890/delete/" \ -H "Authorization: Bearer {jwt_token}" \ -d "confirm=my-share-name" ``` ### Response **Status:** `202 Accepted` ```json { "result": "yes", "current_api_version": "1.0" } ``` ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|----------------------------------------------------------|------------------------------------| | 400 | `1605 (Invalid Input)`| "The `confirm` field provided does not match." | Confirmation mismatch | | 403 | 144499 | "You do not have permissions to access this share." | Lacks admin permission | | 500 | `1663 (Update Failed)` | "There was an internal error processing your request." | Internal error | ### Notes - Soft delete with retention period before permanent removal. - For workspace folder shares, the share reference in the backing folder is cleaned up. - Share link nodes in the parent workspace are also removed. --- ## Archive / Unarchive ``` POST /current/share/{share_id}/archive/ POST /current/share/{share_id}/unarchive/ ``` Archive or unarchive a share. **Auth required. Owner/admin only.** Rooms only -- workspace folder shares cannot be archived. No request body required. ### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/archive/" \ -H "Authorization: Bearer {jwt_token}" ``` ### Response **Status:** `202 Accepted` ```json { "result": "yes", "current_api_version": "1.0" } ``` ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|---------------------------------------------------------------|------------------------------------------| | 400 | `1605 (Invalid Input)`| "Workspace folder shares cannot be archived..." | workspace_folder share | | 400 | `1663 (Update Failed)` | "The share is already archived." / "The share is not archived."| Wrong current state | | 403 | 144499 | "You do not have permissions to access this share." | Lacks admin permission | ### Notes - Archived shares block guest access. - Unarchiving requires the share unarchive feature to be enabled on the billing plan. --- ## Password Protection Available only on **Send** shares with `'Anyone with the link'` access. ### Set/Update Password Set via `password` parameter on create or update (4-128 chars). Send `"null"` or `""` to clear. ### Authenticate with Password ``` POST /current/share/{share_id}/auth/password/ ``` Authenticate with a share password to get a scoped JWT token. **No user auth required.** #### Request Body | Parameter | Type | Required | Description | |------------|--------|----------|-------------------| | `password` | string | Yes | Share password | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/auth/password/" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "password=mysecretpassword" ``` #### Response **Status:** `200 OK` ```json { "result": "yes", "response": { "expires_in": 86400, "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }, "current_api_version": "1.0" } ``` #### Response Fields | Field | Type | Description | |------------------------|---------|-------------------------------------------------| | `response.expires_in` | integer | Token lifetime in seconds (86400 = 24 hours) | | `response.auth_token` | string | JWT token to use in `x-ve-password` header | #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|---------------------|------------------------------------------------------------|----------------------------------------| | 400 | Input validation | "Password is required for authentication." | Missing password field | | 401 | `1650 (Authentication Invalid)` | "This share does not require password authentication." | Not password-protected or not public | | 406 | `1658 (Not Acceptable)`| "Invalid password provided for this share." | Wrong password | | 500 | `1654 (Internal Error)`| "Failed to create authentication token." | JWT creation failure | #### Notes - Token expires after 24 hours. - If the share's password is changed, all previously issued tokens become invalid. - Use the returned token in the `x-ve-password` header for subsequent share requests. --- ## Anonymous File Drop (Guest Auth) Enables anonymous file uploads on public Receive and Exchange shares without requiring account registration. Visitors obtain a scoped JWT via the guest auth endpoint, then use it as a Bearer token for subsequent upload requests. **Requirements:** - Share must have `'Anyone with the link'` access - Share must be Receive or Exchange type - `anonymous_uploads_enabled` must be true on the share - Premium plan required ### Authenticate as Guest ``` POST /current/share/{share_id}/auth/guest/ ``` Obtain a scoped JWT for anonymous file uploads. Creates an ephemeral user account. **No user auth required.** #### Request No body required. POST request only. #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/auth/guest/" ``` #### Response **Status:** `200 OK` ```json { "result": true, "response": { "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 3600 }, "current_api_version": "1.0" } ``` #### Response Fields | Field | Type | Description | |------------------------|---------|------------------------------------------------- | | `response.auth_token` | string | Scoped JWT for anonymous uploads (Bearer token) | | `response.expires_in` | integer | Token lifetime in seconds | #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|-------------------------------------------------------------------|---------------------------------------------------| | 401 | `1650 (Authentication Invalid)` | "Anonymous uploads are not enabled for this share." | Feature not enabled or share not eligible | | 412 | `1685 (Feature Limit)` | "Anonymous uploads require a premium plan." | Org not on premium plan | | 429 | `1671 (Rate Limited)` | Rate limit exceeded | Too many requests from this IP | | 500 | `1654 (Internal Error)` | "Failed to create authentication token." | JWT creation failure | #### Notes - The returned `auth_token` should be used as a Bearer token in the `Authorization` header for subsequent upload requests. - Each guest auth call creates a new ephemeral user account scoped to the share. - Tokens are short-lived; obtain a new token if the previous one expires. ### Anonymous Upload Workflow 1. **Check share eligibility:** `GET /current/share/{share_id}/public/details/` — verify `anonymous_uploads_enabled: true` in the response. 2. **Obtain guest token:** `POST /current/share/{share_id}/auth/guest/` — returns a scoped `auth_token`. 3. **Upload files:** Use the standard upload flow (`POST /current/upload/init/`, chunk upload, `POST /current/upload/complete/`) with `Authorization: Bearer {auth_token}`. 4. **Add to share:** `POST /current/share/{share_id}/storage/addfile/` with the upload key, using the same Bearer token. --- ## Expiration Available only on **portal** shares (not workspace folder). Set the `expires` parameter (datetime format: `YYYY-MM-DD HH:MM:SS`) on create or update. Send `"null"` to clear. When the share expires, access is revoked. Expiration is validated against the billing plan. --- ## Branding & Styling Shares support custom branding through the update endpoint: | Parameter | Type | Description | |---------------------|---------|--------------------------------------| | `accent_color` | string | JSON color object for accent | | `background_color1` | string | JSON color object for primary bg | | `background_color2` | string | JSON color object for secondary bg | | `background_image` | integer | Background image selection (numeric) | | `link_1` | string | JSON link object (custom link #1) | | `link_2` | string | JSON link object (custom link #2) | | `link_3` | string | JSON link object (custom link #3) | Send `"null"` for any JSON field to clear it. --- ## Share Assets ### List Available Asset Types ``` GET /current/share/assets/ ``` Returns the schema of available asset types (e.g., logo, background). **No auth required.** ### List Share Assets ``` GET /current/share/{share_id}/assets/ ``` List assets set on a share. **Auth required. Owner/admin only.** ### Upload/Set Asset ``` POST /current/share/{share_id}/assets/{asset_id}/ ``` Upload an asset (multipart/form-data). **Auth required. Owner/admin only.** | Field | Type | Required | Description | |------------|------|----------|----------------------------------| | `file` | file | Yes | The asset file to upload | | `metadata` | JSON | No | Optional metadata for the asset | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/assets/logo/" \ -H "Authorization: Bearer {jwt_token}" \ -F "file=@logo.png" \ -F 'metadata={"crop_x": 0, "crop_y": 0}' ``` ### Delete Asset ``` DELETE /current/share/{share_id}/assets/{asset_id}/ ``` Remove an asset. **Auth required. Owner/admin only.** ### Read Asset Binary ``` GET /current/share/{share_id}/assets/{asset_id}/read/ HEAD /current/share/{share_id}/assets/{asset_id}/read/ ``` Returns raw binary data of a share asset. **Auth optional for public shares.** Works on archived shares. | Query Parameter | Type | Required | Description | |-----------------|---------|----------|-------------------------------------------------------| | `width` | integer | No | Requested width (max 4096). Background images only. | | `height` | integer | No | Requested height (max 4096). Background images only. | --- ## Share Members ### Permission Levels | Level | Value | Description | |--------|-------|---------------------------------------------| | Owner | 1000 | Full control and ownership | | Admin | 500 | Administrative access, manage members | | Member | 100 | Standard member access (default for new) | | Guest | 50 | Limited guest access | | View | 20 | Read-only access | ### Notification Options | Value | Description | |------------------------------------------------|---------------------------------------------| | `"Do not notify me"` | No notifications | | `"Notify me in app"` | In-app only (default) | | `"Notify me in app and via email"` | In-app and email | | `"Notify me via app, email and text message"` | All notification channels | ### Add Member or Send Invitation ``` POST /current/share/{share_id}/members/{email_or_user_id}/ ``` Use a 19-digit user ID to add an existing user directly, or an email address to send an invitation. **Auth required. Owner/admin only.** #### Request Body (Adding Existing User) | Field | Type | Required | Default | Description | |---------------------|--------|----------|----------------------|--------------------------------------------------------------------------------| | `permissions` | string | No | `member` | `owner`, `admin`, `member`, `guest`, `view`. Cannot be `owner` (use transfer). | | `notify_options` | string | No | `"Notify me in app"` | Notification preference | | `expires` | string | No | - | Membership expiration (`YYYY-MM-DD HH:MM:SS`). `null` or `""` to clear. | | `force_notification`| boolean| No | - | Resend notification email (60-second cooldown after initial add). | #### Request Body (Inviting by Email) | Field | Type | Required | Default | Description | |----------------------|--------|----------|----------|--------------------------------------------| | `permissions` | string | No | `member` | Permission level for the invitation | | `message` | string | No | - | Custom message for the invitation email | | `invitation_expires` | string | No | - | Invitation expiration datetime | #### Request Example ```bash # Add existing user by ID curl -X POST "https://api.fast.io/current/share/12345678901234567890/members/98765432109876543210/" \ -H "Authorization: Bearer {jwt_token}" \ -d '{"permissions": "member"}' # Invite by email curl -X POST "https://api.fast.io/current/share/12345678901234567890/members/newuser@example.com/" \ -H "Authorization: Bearer {jwt_token}" \ -d '{"permissions": "guest", "message": "Join our shared files!"}' ``` #### Response (Invitation Created) ```json { "result": "yes", "response": { "invitation": { "id": "55667788990011223344", "inviter": "Jane Smith", "invitee_email": "newuser@example.com", "invitee_uid": null, "accepted_uid": null, "entity_type": "share", "share": { "id": "12345678901234567890", "name": "Project Files" }, "state": "pending", "consumed": false, "created": "2025-01-15 10:30:00", "updated": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" } }, "current_api_version": "1.0" } ``` #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|--------------------------------------------------------------------------------|--------------------------------| | 400 | `1680 (Cannot Add As Owner)`| "Adding a member as an owner is not allowed" | Tried to add as owner | | 400 | `1605 (Invalid Input)`| "You cannot create a membership for the share owner." | Target is share owner | | 400 | `1605 (Invalid Input)`| "You cannot add, update, or delete a membership with a higher permission..." | Permission exceeds caller | | 403 | `1680 (Access Denied)` | "You do not have permission to manage the members of this Share." | Lacks member edit permission | | 403 | `1685 (Feature Limit)` | "Share invitation limit reached: X of Y invitations used" | Invitation quota exceeded | ### Remove Member ``` DELETE /current/share/{share_id}/members/{user_id}/ ``` Remove a member. **Auth required. Owner/admin only.** Cannot remove the last owner. ### Leave Share (Self-Removal) ``` DELETE /current/share/{share_id}/member/ ``` Leave a share. **Auth required.** Owners cannot leave; must transfer ownership first. Works on archived/expired shares. ### Member Details ``` GET /current/share/{share_id}/member/{user_id}/details/ ``` Get detailed membership info for a specific user. **Auth required.** #### Response ```json { "result": "yes", "response": { "user": { "id": "98765432109876543210", "account_type": "human", "email_address": "member@example.com", "first_name": "John", "last_name": "Doe", "permissions": "admin", "status": "active", "invite": null, "notify": "Notify me in app", "expires": null } }, "current_api_version": "1.0" } ``` ### Update Member ``` POST /current/share/{share_id}/member/{user_id}/update/ ``` Update permissions, notification settings, or expiration. **Auth required. Owner/admin only.** | Field | Type | Required | Description | |-----------------|--------|----------|---------------------------------------------------------------| | `permissions` | string | No | `admin`, `member`, `guest`, `view`. Cannot set to `owner`. | | `notify_options`| string | No | Notification preference | | `expires` | string | No | Membership expiration datetime. `null`/`""` to clear. | ### Transfer Ownership ``` GET /current/share/{share_id}/member/{user_id}/transfer_ownership/ ``` Transfer share ownership to another member. **Auth required. Owner only.** Current owner is demoted to admin. ### List Members ``` GET /current/share/{share_id}/members/list/ ``` List all members. **Auth required.** #### Response ```json { "result": "yes", "response": { "users": [ { "id": "11111111111111111111", "account_type": "human", "email_address": "owner@example.com", "first_name": "Jane", "last_name": "Smith", "permissions": "owner", "status": "active", "invite": null, "notify": "Notify me in app", "expires": null }, { "id": "55667788990011223344", "account_type": "human", "email_address": "invited@example.com", "first_name": "invited@example.com", "last_name": "", "permissions": "guest", "status": "pending", "invite": { "id": "55667788990011223344", "created": "2025-01-15 10:30:00", "expires": "2025-02-15 10:30:00" }, "notify": "Notify me in app", "expires": null } ] }, "current_api_version": "1.0" } ``` ### Pending Members When a user is invited to a share by email but does not yet have a Fast.io account, they appear as a **pending member** in the member list. Pending members are placeholders that allow teams to pre-assign workflow items before the invitee signs up. **How pending members appear in responses:** - The `status` field is `"pending"` (vs `"active"` for registered users). - An `invite` object is included with basic invitation details (`id`, `created`, `expires`). - `email_address` shows the invited email address. - `first_name` is set to the invited email address; `last_name` is empty. **Workflow assignments:** Pending members can be assigned to approvals, tasks, and todos before they create an account. Their user ID is valid and works with all assignment endpoints. On shares, guests (including pending guests) can only be assigned to approvals. **Account claim:** When the invited user signs up or accepts the invitation with an existing account, their status transitions from `"pending"` to `"active"`. All existing workflow assignments are preserved and transfer automatically. **Removal:** To remove a pending member, delete their invitation using the invitation endpoints (see List Invitations, Delete Invitation below). Deleting the invitation removes the pending member and unassigns any workflow items associated with them. **Notifications:** Pending members do not receive in-app or email notifications until they claim their account. --- ### Join Share ``` POST /current/share/{share_id}/members/join/ POST /current/share/{share_id}/members/join/{invitation_key}/ POST /current/share/{share_id}/members/join/{invitation_key}/accept/ POST /current/share/{share_id}/members/join/{invitation_key}/decline/ ``` Join a share via self-join or invitation. **Auth required.** **Self-join rules** (without invitation key): | Access Option | Who Can Self-Join | |-------------------------------------------------|---------------------------------------------------------------| | `'Only members of the Share or Workspace'` | Members of the share or its parent workspace | | `'Members of the Share, Workspace or Org'` | Members of the share, parent workspace, or parent org | | Other values | Self-join blocked; invitation required | Self-joined members receive `member` permission with no expiration. Only `notify_options` is respected from input. ### List Invitations ``` GET /current/share/{share_id}/members/invitations/list/ GET /current/share/{share_id}/members/invitations/list/{state}/ ``` List invitations, optionally filtered by state (`pending`, `accepted`, `declined`). **Auth required.** ### Update Invitation ``` POST /current/share/{share_id}/members/invitation/{invitation_id}/ ``` Update an invitation. `{invitation_id}` can be a numeric ID or an email address. **Auth required.** | Field | Type | Required | Description | |-----------------|--------|----------|------------------------------------------------------------------| | `state` | string | No | `pending`, `accepted`, `declined` | | `permissions` | string | No | Updated permission level: `admin`, `member`, `guest`, `view` | | `notify_options`| string | No | Updated notification preference | | `expires` | string | No | Updated membership expiration datetime | ### Delete Invitation ``` DELETE /current/share/{share_id}/members/invitation/{invitation_id}/ ``` Revoke an invitation. `{invitation_id}` can be a numeric ID or an email address. **Auth required.** --- ## Share Discovery ### List All Shares ``` GET /current/shares/all/ ``` List all accessible shares (joined, invited, owned). **Auth required.** Orphaned workspace folder shares are automatically filtered out. Returns full permission and settings objects for each share. #### Response Fields Each share in the `shares` array includes the same fields as the details endpoint, plus: | Field | Type | Description | |-----------------------------------|-------------|------------------------------------------------------| | `shares[].user_status` | string | User's membership status (e.g., `joined`, `invited`) | | `shares[].folder_node_id` | string\|null| Backing folder node ID (workspace_folder only) | | `shares[].is_orphaned` | boolean | Whether backing folder was deleted | ### List Available Shares ``` GET /current/shares/available/ ``` List shares the user has joined or owns (excludes pending invitations). **Auth required.** Does not include parent workspace/org information. ### Check Share Name Availability ``` GET /current/shares/check/name/{name}/ ``` Check if a share custom name is available. **Auth required.** **Status `202 Accepted`** = name available. | HTTP Status | Code | Message | Cause | |-------------|--------------------------|--------------------------------------------------|--------------------| | 400 | `1605 (Invalid Input)`| "An invalid share name was supplied." | Fails validation | | 406 | `1658 (Not Acceptable)` | "The supplied share name is already in use." | Name already taken | ### List Shares in Workspace ``` GET /current/workspace/{workspace_id}/list/shares/ ``` List shares belonging to a workspace. **Auth required.** Paginated with offset-based pagination (`limit`/`offset`). | Query Parameter | Type | Required | Description | |-----------------|--------|----------|------------------------------------------| | `limit` | integer| No | Number of results to return | | `offset` | integer| No | Number of results to skip | | `archived` | string | No | Boolean string. Filter by archived state.| ### List User's Shares ``` GET /current/user/me/list/shares/ ``` List all shares accessible to the current user (owned, joined, invited). **Auth required.** Paginated (`limit`/`offset`). Includes parent workspace and org information. | Query Parameter | Type | Required | Description | |-----------------|--------|----------|------------------------------------------| | `limit` | integer| No | Number of results to return | | `offset` | integer| No | Number of results to skip | | `archived` | string | No | Boolean string. Filter by archived state.| --- ## Import Share into Workspace ``` POST /current/workspace/{workspace_id}/import/share/{share_id}/ ``` Import a user-owned share into a workspace. **Auth required.** Caller must be the share owner with no other co-owners. The share must currently be user-owned (not already in a workspace). Archived shares are automatically unarchived during import. ### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|---------------------------------------------------------------------|----------------------------------| | 400 | `1605 (Invalid Input)`| "This share is not owned by you and cannot be imported." | Share not user-owned | | 400 | `1605 (Invalid Input)`| "The share has multiple owners..." | Multiple co-owners exist | | 403 | `1685 (Feature Limit)` | "The workspace has reached its share limit." | Share quota exceeded | | 500 | `1654 (Internal Error)` | "Failed to import the share to the workspace." | Internal error | --- ## Share Workflow Shares support workflow features (task lists, todo items, and approvals) when enabled. Workflow requires the `workflow` billing plan feature. ### Enable Workflow ``` POST /current/share/{share_id}/workflow/enable/ ``` Enable workflow features on a share. **Auth required. Admin only.** Idempotent -- returns success if already enabled. #### Response **Status:** `200 OK` ```json { "result": true, "response": { "message": "Workflow features enabled", "workflow": true }, "current_api_version": "1.0" } ``` ### Disable Workflow ``` POST /current/share/{share_id}/workflow/disable/ ``` Disable workflow features on a share. **Auth required. Admin only.** Idempotent -- returns success if already disabled. #### Response **Status:** `200 OK` ```json { "result": true, "response": { "message": "Workflow features disabled", "workflow": false }, "current_api_version": "1.0" } ``` ### List Task Lists ``` GET /current/share/{share_id}/tasks/ ``` List all task lists for a share. **Auth required. Workflow view permission required.** Supports offset-based pagination (`limit`/`offset`). Supports `format=markdown` query parameter for markdown output. #### Response **Status:** `200 OK` ```json { "result": true, "response": { "task_lists": [ { "id": "aBcDeFgHiJ", "profile_id": "12345678901234567890", "name": "Sprint 1", "description": "First sprint tasks", "created_by": "98765432109876543210", "properties": {}, "sort_order": 0, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` ### Create Task List ``` POST /current/share/{share_id}/tasks/create/ ``` Create a new task list in a share. **Auth required. Workflow modify permission required.** Request body is JSON. #### Request Body | Field | Type | Required | Description | |---------------|---------|----------|--------------------------------------| | `name` | string | Yes | Task list name | | `description` | string | No | Task list description | | `properties` | object | No | Custom properties (default `{}`) | | `sort_order` | integer | No | Sort position (default `0`) | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/tasks/create/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"name": "Sprint 1", "description": "First sprint tasks"}' ``` #### Response **Status:** `200 OK` ```json { "result": true, "response": { "task_list": { "id": "aBcDeFgHiJ", "profile_id": "12345678901234567890", "name": "Sprint 1", "description": "First sprint tasks", "created_by": "98765432109876543210", "properties": {}, "sort_order": 0, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|----------------------------|---------------------------| | 400 | `1605 (Invalid Input)`| "Invalid task list name" | Name fails validation | | 400 | `1605 (Invalid Input)`| "Invalid description" | Description fails validation | | 400 | `1605 (Invalid Input)`| "Invalid JSON in request body" | Malformed JSON body | ### Reorder Task Lists ``` POST /current/share/{share_id}/tasks/reorder/ ``` Bulk reorder task lists within a share. **Auth required. Workflow modify permission required.** Request body is JSON. Prevents concurrent reordering. #### Request Body | Field | Type | Required | Description | |---------|-------|----------|---------------------------------------------------------| | `order` | array | Yes | Non-empty array of `{id, sort_order}` objects | Each entry in `order`: | Field | Type | Required | Description | |--------------|---------|----------|----------------------------| | `id` | string | Yes | Task list opaque ID | | `sort_order` | integer | Yes | New sort position | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/tasks/reorder/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"order": [{"id": "aBcDeFgHiJ", "sort_order": 0}, {"id": "kLmNoPqRsT", "sort_order": 1}]}' ``` #### Response **Status:** `200 OK` ```json { "result": true, "response": { "reordered": 2, "profile_id": "12345678901234567890" }, "current_api_version": "1.0" } ``` #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|----------------------------------------------------------|----------------------------------------| | 400 | `1605 (Invalid Input)`| "order must be a non-empty array of {id, sort_order}..." | Missing or empty order array | | 400 | `1605 (Invalid Input)`| "Each order entry must have id and sort_order fields..." | Malformed entry | | 400 | `1605 (Invalid Input)`| "Task list ID ... does not belong to this share" | ID not owned by this share | --- ### List Todo Items ``` GET /current/share/{share_id}/todos/ ``` List all todo items for a share. **Auth required. Workflow view permission required.** Supports offset-based pagination (`limit`/`offset`). Supports `format=markdown` query parameter for markdown output. #### Response **Status:** `200 OK` ```json { "result": true, "response": { "todos": [ { "id": "aBcDeFgHiJ", "profile_id": "12345678901234567890", "title": "Review document", "done": 0, "assignee_id": "98765432109876543210", "sort_order": 0, "created_by": "98765432109876543210", "properties": null, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` ### Create Todo Item ``` POST /current/share/{share_id}/todos/create/ ``` Create a new todo item in a share. **Auth required. Workflow modify permission required.** Request body is JSON. #### Request Body | Field | Type | Required | Description | |---------------|---------|----------|--------------------------------------------| | `title` | string | Yes | Todo item title | | `assignee_id` | string | No | 19-digit numeric user ID to assign | | `sort_order` | integer | No | Sort position (default `0`) | | `properties` | object | No | Custom properties | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/todos/create/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"title": "Review document", "assignee_id": "98765432109876543210"}' ``` #### Response **Status:** `200 OK` ```json { "result": true, "response": { "todo": { "id": "aBcDeFgHiJ", "profile_id": "12345678901234567890", "title": "Review document", "done": 0, "assignee_id": "98765432109876543210", "sort_order": 0, "created_by": "98765432109876543210", "properties": null, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|----------------------------------|---------------------------| | 400 | `1605 (Invalid Input)`| "Invalid title" | Title fails validation | | 400 | `1605 (Invalid Input)`| "Invalid assignee ID format" | Bad assignee ID | | 400 | `1605 (Invalid Input)`| "Invalid JSON in request body" | Malformed JSON body | ### Bulk Toggle Todos ``` POST /current/share/{share_id}/todos/bulk-toggle/ ``` Bulk toggle the done status for multiple todo items. **Auth required. Workflow modify permission required.** Request body is JSON. #### Request Body | Field | Type | Required | Description | |------------|---------|----------|--------------------------------------------| | `todo_ids` | array | Yes | Non-empty array of todo item opaque IDs | | `done` | boolean | Yes | `true` to mark done, `false` to unmark | #### Request Example ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/todos/bulk-toggle/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"todo_ids": ["aBcDeFgHiJ", "kLmNoPqRsT"], "done": true}' ``` #### Response **Status:** `200 OK` ```json { "result": true, "response": { "toggled": 2, "done": true }, "current_api_version": "1.0" } ``` #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|----------------------------------------|----------------------------| | 400 | `1605 (Invalid Input)`| "todo_ids must be a non-empty array" | Missing or empty todo_ids | | 400 | `1605 (Invalid Input)`| "done field is required (true or false)"| Missing done field | | 400 | `1605 (Invalid Input)`| "Invalid todo ID format: ..." | Malformed opaque ID | --- ### List Approvals ``` GET /current/share/{share_id}/approvals/ ``` List approvals for a share, with optional status filter. **Auth required. Workflow view permission required.** Supports offset-based pagination (`limit`/`offset`). Supports `format=markdown` query parameter for markdown output. #### Query Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|------------------------------------------------------------| | `status` | string | No | Filter by status: `pending`, `approved`, `rejected` | | `limit` | integer| No | Number of results to return | | `offset` | integer| No | Number of results to skip | | `format` | string | No | Set to `markdown` for markdown output | #### Response **Status:** `200 OK` ```json { "result": true, "response": { "approvals": [ { "id": "aBcDeFgHiJ", "entity_type": "task", "entity_id": "kLmNoPqRsT", "profile_id": "12345678901234567890", "requested_by": "98765432109876543210", "description": "Please review this task", "status": "pending", "approver_id": "11223344556677889900", "resolved_by": null, "resolved_at": null, "comment": null, "deadline": "2026-02-01T00:00:00+00:00", "node_id": null, "properties": {}, "created": "2026-01-15T10:30:00+00:00", "updated": "2026-01-15T10:30:00+00:00" } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` #### Response Fields | Field | Type | Description | |--------------------------------|-------------|-----------------------------------------------------| | `approvals[].id` | string | Approval opaque ID | | `approvals[].entity_type` | string | Entity type: `task`, `node`, `worklog_entry` | | `approvals[].entity_id` | string | Opaque ID of the entity being approved | | `approvals[].profile_id` | string | 19-digit share profile ID | | `approvals[].requested_by` | string | 19-digit user ID of requester | | `approvals[].description` | string\|null| Description of the approval request | | `approvals[].status` | string | `pending`, `approved`, or `rejected` | | `approvals[].approver_id` | string\|null| 19-digit user ID designated to approve | | `approvals[].resolved_by` | string\|null| 19-digit user ID who resolved the approval | | `approvals[].resolved_at` | string\|null| Resolution timestamp (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].comment` | string\|null| Resolution comment | | `approvals[].deadline` | string\|null| Approval deadline (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].node_id` | string\|null| Associated storage node opaque ID | | `approvals[].properties` | object\|null| Custom properties | | `approvals[].created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `approvals[].updated` | string | Last-updated timestamp (`YYYY-MM-DD HH:MM:SS`) | #### Error Responses | HTTP Status | Code | Message | Cause | |-------------|--------------------------|-------------------------------------------------------------|-----------------------------| | 400 | `1605 (Invalid Input)`| "Invalid status filter. Valid values: pending, approved, rejected" | Bad status query param | | 403 | - | "Workflow features are only available to share members..." | Lacks workflow permission | ### Workflow Error Responses (Shared) All workflow endpoints (tasks, todos, approvals, enable/disable) share these common errors: | HTTP Status | Code | Message | Cause | |-------------|--------------------------|--------------------------------------------------------------|-------------------------------------| | 403 | 144499 | "You do not have permissions to access this share." | Lacks admin permission (enable/disable) | | 403 | - | "Workflow features are only available to share members..." | Lacks workflow view/modify permission | | 412 | `1685 (Feature Limit)` | Workflow feature not available | Billing plan does not include workflow | --- ## Share AI Shares support AI features when `intelligence` is enabled. ### Auto-Generate OG Image ``` GET /current/share/{share_id}/ai/autoog/ ``` Generate an Open Graph image for the share. **Auth required.** ### Auto-Generate Title & Description ``` POST /current/share/{share_id}/ai/autotitle/ ``` Generate a title and description for the share based on its contents. **Auth required.** ### AI Chat Share AI chat follows the same patterns as workspace AI chat. Replace `/workspace/{id}` with `/share/{id}` in all chat endpoints. For full AI chat documentation, see `ai.txt`. **Endpoints:** ``` POST /current/share/{share_id}/ai/chat/ -- create chat GET /current/share/{share_id}/ai/chat/list/ -- list chats GET /current/share/{share_id}/ai/chat/{chat_id}/details/ -- chat details POST /current/share/{share_id}/ai/chat/{chat_id}/update/ -- update chat DELETE /current/share/{share_id}/ai/chat/{chat_id}/ -- delete chat POST /current/share/{share_id}/ai/chat/{chat_id}/message/ -- send message GET /current/share/{share_id}/ai/chat/{chat_id}/messages/list/ -- list messages GET /current/share/{share_id}/ai/chat/{chat_id}/message/{msg_id}/details/ -- message details GET /current/share/{share_id}/ai/chat/{chat_id}/message/{msg_id}/read/ -- stream response (SSE) POST /current/share/{share_id}/ai/chat/{chat_id}/publish/ -- publish chat POST /current/share/{share_id}/ai/share/ -- AI share markdown ``` --- ## QuickShare (Workspace Feature) QuickShare creates a temporary public link for a single file. This is a **workspace** storage feature, not a share management feature, but is related to sharing. ### Create QuickShare ``` POST /current/workspace/{workspace_id}/storage/{node_id}/quickshare/ ``` **Auth required.** Constraints: single file only (not folders), max 1 GB, default expiration 3 hours, maximum 7 days. Supports `expires` (seconds) or `expires_at` (ISO 8601 or `YYYY-MM-DD HH:MM:SS` datetime) parameters. Cleaned up by background job after expiration. ### Public Access Endpoints (No Auth Required) #### QuickShare Details ``` GET /current/quickshare/{opaque_id}/details/ ``` Returns metadata including file information, creator, expiration, and download limit status. **Response:** ```json { "result": "yes", "response": { "quickshare": { "id": "aBcDeFgHiJ", "node": { "id": "12345678901234567890", "type": "file", "name": "presentation.pdf", "size": 5242880, "mimetype": "application/pdf", "mimecategory": "document", "summary": {"title": "Quarterly Presentation", "short": "Q4 slides overview"}, "metadata": {"title": "Custom Title", "short": "Custom description"} }, "creator_uid": { "id": "98765432109876543210", "account_type": "human", "first_name": "Jane", "last_name": "Doe" }, "limit_exceeded": false, "expires": "2024-01-15 13:30:00", "created": "2024-01-15 10:30:00" } }, "current_api_version": "1.0" } ``` #### Download File ``` GET /current/quickshare/{opaque_id}/storage/read/ ``` Returns raw binary file content with `Content-Type` and `Content-Disposition` headers. **Transfer Limits:** - Max total bytes: 10 GB - Max multiplier: 100x file size - Once either limit is reached, `limit_exceeded` is set permanently. #### Read Note Content ``` GET /current/quickshare/{opaque_id}/storage/readnote/ ``` Returns the content of a note/markdown file as JSON. Unlike the binary `/read/` endpoint, this returns the sanitized markdown content as a string within the JSON response along with the note resource. **Response:** ```json { "result": "yes", "response": { "content": "# Note content here\n\nMarkdown text...", "note": { "id": "{opaque_note_id}", "type": "note", "name": "my-note.md" } } } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `content` | string | Sanitized markdown content | | `note` | object | Note resource (unpermissioned format) | **Error responses:** | HTTP Status | Code | Cause | |-------------|------|-------| | 400 | `1605 (Invalid Input)` | Node is not a note | | 404 | `1609 (Not Found)` | Quickshare not found, or note is in trash | | 429 | `1656 (Bandwidth Limit)` | Transfer limit reached | | 403 | `1680 (Access Denied)` | File access denied (virus/DMCA/locked) | #### Preview File ``` GET /current/quickshare/{opaque_id}/storage/preview/{preview_type}/read/ GET /current/quickshare/{opaque_id}/storage/preview/{preview_type}/read/file/{filename} ``` Preview types: `binary`, `thumbnail`, `image`, `hls_stream`, `pdf`, `spreadsheet`, `audio`, `mp4`. **Preview Limits:** Max 20x file size for total preview bytes. Multi-file previews (e.g., HLS) return a `307 Temporary Redirect` to a sub-file endpoint. #### QuickShare Error Responses | HTTP Status | Code | Message | Cause | |-------------|----------------------|------------------------------------------------------------------------|------------------------| | 400 | `1605 (Invalid Input)` | Validation error | Invalid opaque ID | | 404 | `1609 (Not Found)`| "Quickshare not found." | No such QuickShare | | 429 | `1656 (Bandwidth Limit)`| "You have exceeded the bandwidth policy for this Quickshare." | Transfer limit reached | | 403 | `1680 (Access Denied)` | "You have reached the maximum number of previews for this Quickshare." | Preview limit reached | --- ## Share Storage Share storage follows the same API patterns as workspace storage. See `storage.txt` for full endpoint documentation. Replace `/workspace/{workspace_id}` with `/share/{share_id}` in all storage paths. Available operations: - `addfile` -- add file from upload - `createfolder` -- create folder - `list` -- list folder contents (keyset pagination) - `details` -- node details - `update` -- rename, replace content - `move` -- move within share - `copy` -- copy within share - `transfer` -- copy to another storage instance - `delete` -- move to trash - `purge` -- permanently delete from trash - `restore` -- restore from trash - `restore-version` -- restore previous version - `versions` -- list version history - `read` -- download file - `requestread` -- get download token - `zip` -- download folder as ZIP - `search` -- keyword search - `preview` -- file previews (preauthorize, read, token-based read) - `transform` -- image transforms (status, request) - `lock` -- file locking (acquire, heartbeat, release, status) **Not available in shares:** `addlink`, `createnote`, `updatenote`, `quickshare`, `transform/requestread` > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # File Upload Base URL: `https://api.fast.io/current/` All upload endpoints require authentication unless noted: `Authorization: Bearer {jwt_token}` Request format: `multipart/form-data` for file data, `application/x-www-form-urlencoded` for non-file requests. --- ## Overview Fast.io supports four upload flows: 1. **Small files (< 4 MB)** -- Single-request upload. Send the file as a multipart chunk in the session creation request. Optionally auto-add to storage in the same call. 2. **Large files (>= 4 MB)** -- Chunked upload. Create a session, upload chunks (up to 3 in parallel), trigger assembly, poll until stored. 3. **Stream upload** -- Upload a file of unknown size in a single request (stream mode). Create a session with `stream=true`, then POST the raw file body to the stream endpoint. 4. **Web upload (URL import)** -- Import files from any HTTP/HTTPS URL. The server downloads and uploads the file asynchronously. All direct upload flows produce an upload session with a unique `id` (OpaqueId). Once the upload reaches `stored` status, the file is ready to use. --- ## Upload Constraints | Constraint | Value | |---|---| | Single-call upload max size | 4 MB (4,194,304 bytes) | | Chunk size | Plan-dependent (query `/upload/limits/` for exact values) | | Last chunk | May be smaller than the plan chunk size | | Max parallel chunk uploads per session | 3 | | Max undersized chunks per session | 1 (the final chunk only) | | Chunk ordering | 1-based (first chunk is `order=1`) | | Supported hash algorithms | `md5`, `sha1`, `sha256`, `sha384` | | `relative_path` max length | 8192 characters | | `relative_path` | Omit entirely if empty -- do NOT send as empty string | | `creator` format | 1-150 chars, alphanumeric and hyphens only (`/^[a-zA-Z0-9\-]+$/`) | | Max file size | Plan-dependent (up to 40 GB) | | Max concurrent sessions | Plan-dependent (150 for Free, 7500 for Pro/Business) | | Long-poll max wait | 590 seconds | | Stream upload | 1 stream per session | Exactly one stream upload allowed per stream-mode session | --- ## Upload Status Values | Status | Meaning | Action | |---|---|---| | `ready` | Session created, awaiting chunks | Upload chunks | | `uploading` | Chunks being received | Continue uploading | | `assemble` | Assembly queued | Keep polling | | `assembling` | Assembly in progress | Keep polling | | `complete` | Assembly done, awaiting storage import. File is NOT accessible for download or preview -- only import to storage locations is possible (via auto-add or `addfile`). Can be imported to multiple locations. | Keep polling (or use `addfile` if not auto-adding) | | `store` | Storage import queued | Keep polling | | `storing` | Being imported to storage | Keep polling | | `stored` | Fully complete -- file assembled and in storage | Done. `new_file_id` available. | | `assembly_failed` | Assembly failed | Handle error | | `store_failed` | Storage import failed | Handle error | **Terminal states:** `stored`, `assembly_failed`, `store_failed`. Stop polling when you reach one of these. --- ## Workflow: Small File Upload (< 4 MB) A single request creates the session and uploads the file. Optionally auto-adds to storage. ### Step 1: Upload in one request ``` POST /current/upload/ Content-Type: multipart/form-data Authorization: Bearer {jwt_token} ``` **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | File name including extension (1-255 chars) | | `size` | integer | Yes | File size in bytes (must match actual file) | | `chunk` | file | Yes | The file binary data (multipart field) | | `action` | string | No | `"create"` for new file, `"update"` for file replacement | | `instance_id` | string | Required if action=create | Workspace or share profile ID (19-digit numeric) | | `file_id` | string | Required if action=update | OpaqueId of the existing file to replace | | `folder_id` | string | No | Target folder OpaqueId or `"root"` for storage root | | `hash` | string | No | Hash of the full file. Must be provided with `hash_algo`. | | `hash_algo` | string | No | Hash algorithm: `"md5"`, `"sha1"`, `"sha256"`, or `"sha384"` | | `relative_path` | string | No | For folder uploads, relative path for auto folder creation (max 8192 chars). Omit entirely if not applicable. | | `org` | string | No | Organization ID for billing limit resolution (only used when no `action` is specified) | | `creator` | string | No | Client identifier string (1-150 chars, alphanumeric and hyphens only) | **curl example (small file with auto-add to workspace):** ```bash curl -X POST "https://api.fast.io/current/upload/" \ -H "Authorization: Bearer {jwt_token}" \ -F "name=notes.txt" \ -F "size=1024" \ -F "action=create" \ -F "instance_id=12345678901234567890" \ -F "folder_id=root" \ -F "chunk=@notes.txt" ``` **Response (201 Created):** ```json { "result": "yes", "response": { "id": "u1abc-defgh-ijklm-nopqr-stuvw-xyz123", "creator": "my-web-client", "new_file_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4" }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `result` | string | `"yes"` on success | | `response.id` | string | Upload session OpaqueId | | `response.creator` | string | Echoed back only if `creator` was provided in the request | | `response.new_file_id` | string | OpaqueId of the created storage node. Only present for single-call uploads with an upload target (`instance_id`). | When `instance_id` and `folder_id` are provided, the file is automatically added to storage. No separate polling or `addfile` step is needed. **Response (without `instance_id`, 201 Created):** ```json { "result": "yes", "response": { "id": "u1abc-defgh-ijklm-nopqr-stuvw-xyz123" }, "current_api_version": "1.0" } ``` Without a target, only the upload session `id` is returned. Use `addfile` to place the file in storage after the upload completes. **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1605 (Invalid Input)` | 400 | "The size was not supplied." | | `1605 (Invalid Input)` | 400 | "The file name is not valid." | | `1605 (Invalid Input)` | 400 | "Invalid share or workspace instance ID." | | `1658 (Not Acceptable)` | 406 | "This file type is not allowed for upload on your plan." | | `1685 (Feature Limit)` | 403 | "The file size exceeds single call upload, use chunks." | | `1685 (Feature Limit)` | 403 | "You have created too many upload sessions..." | | `1685 (Feature Limit)` | 403 | "The size is too large for the account plan." | | `1685 (Feature Limit)` | 403 | "The total size of all active upload sessions exceeds the limit." | | `1605 (Invalid Input)` | 400 | "The hash algorithm provided is not valid." | | `1605 (Invalid Input)` | 400 | "The hash provided is not valid." | | `1605 (Invalid Input)` | 400 | "The hash algorithm was provided but not the hash." | | `1658 (Not Acceptable)` | 406 | "We were unable to create the upload session..." | --- ## Workflow: Large File Upload (Chunked) ### Step 1: Create upload session ``` POST /current/upload/ Content-Type: application/x-www-form-urlencoded Authorization: Bearer {jwt_token} ``` **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | File name including extension (1-255 chars) | | `size` | integer | Yes | Total file size in bytes | | `action` | string | Yes | `"create"` for new file, `"update"` for file replacement | | `instance_id` | string | Required if action=create | Workspace or share profile ID (19-digit numeric) for auto-add to storage after assembly | | `file_id` | string | Required if action=update | OpaqueId of existing file to replace | | `folder_id` | string | No | Target folder OpaqueId or `"root"` for storage root | | `hash` | string | No | SHA-256 hex hash of the full file for integrity verification. Must be provided with `hash_algo`. | | `hash_algo` | string | No | Hash algorithm: `"md5"`, `"sha1"`, `"sha256"`, or `"sha384"` | | `relative_path` | string | No | For folder uploads, relative path for auto folder creation (max 8192 chars). Omit entirely if not applicable. | | `org` | string | No | Organization ID for billing limit resolution (only used when no `action` is specified) | | `creator` | string | No | Client identifier string (1-150 chars, alphanumeric and hyphens only) | **curl example:** ```bash curl -X POST "https://api.fast.io/current/upload/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=annual-report.pdf" \ -d "size=52428800" \ -d "action=create" \ -d "instance_id=12345678901234567890" \ -d "hash_algo=sha256" \ -d "hash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ``` **Response (201 Created):** ```json { "result": "yes", "response": { "id": "u1abc-defgh-ijklm-nopqr-stuvw-xyz123" }, "current_api_version": "1.0" } ``` ### Step 2: Upload chunks ``` POST /current/upload/{upload_id}/chunk/?order={n}&size={bytes} Content-Type: multipart/form-data Authorization: Bearer {jwt_token} ``` **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The upload session ID from Step 1 | **Query parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `order` | integer | Yes | 1-based chunk number (first chunk = 1). Must not exceed plan's chunk limit. | | `size` | integer | Yes | Size of this chunk in bytes. Must match the actual uploaded file size. | | `hash` | string | No | Hash of this chunk. Must be provided with `hash_algo`. | | `hash_algo` | string | No | Hash algorithm: `"md5"`, `"sha1"`, `"sha256"`, or `"sha384"` | **Request body (multipart/form-data):** | Field | Type | Required | Description | |---|---|---|---| | `chunk` | file | Yes | Binary chunk data | Upload up to 3 chunks in parallel. The last chunk may be smaller. Only 1 undersized chunk is allowed per session. When all chunks have been uploaded (total bytes equal the declared file size), auto-finalization triggers automatically. You can still call the complete endpoint explicitly. **curl example:** ```bash curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/chunk/?order=1&size=5242880&hash_algo=sha256&hash=abc123def456..." \ -H "Authorization: Bearer {jwt_token}" \ -F "chunk=@chunk_001.bin" ``` **Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1605 (Invalid Input)` | 400 | "The session `id` provided is not valid." | | `1658 (Not Acceptable)` | 406 | "The session `id` provided is not in a valid state to accept a chunk." | | `1605 (Invalid Input)` | 400 | "No `order` supplied" | | `1605 (Invalid Input)` | 400 | "The order provided for this chunk is not valid..." | | `1605 (Invalid Input)` | 400 | "The size was not supplied." | | `1685 (Feature Limit)` | 403 | "The size is too large for the account plan." | | `1685 (Feature Limit)` | 403 | "The size is too small for the account plan." | | `1685 (Feature Limit)` | 403 | "You have exceeded the maximum number of chunks..." | | `1685 (Feature Limit)` | 403 | "The combined chunk size exceeds the size for this session." | | `1605 (Invalid Input)` | 400 | "The upload chunk failed or was the wrong size..." | | `1605 (Invalid Input)` | 400 | "The chunk failed to hash properly..." | | `1654 (Internal Error)` | 500 | "The chunk failed to be stored..." | ### Step 3: Trigger assembly ``` POST /current/upload/{upload_id}/complete/ Authorization: Bearer {jwt_token} ``` **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The upload session ID | **Query parameters (optional):** | Parameter | Type | Required | Description | |---|---|---|---| | `hash` | string | No | Final file hash for validation. Can be provided/updated at completion time. Must be provided with `hash_algo`. | | `hash_algo` | string | No | Hash algorithm: `"md5"`, `"sha1"`, `"sha256"`, or `"sha384"` | No body parameters required. Triggers asynchronous assembly of all uploaded chunks. If the session is already in a completed or processing state (`complete`, `assemble`, `assembling`, `store`, `storing`, `stored`), the endpoint returns `200 OK` immediately without error. **curl example:** ```bash curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/complete/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (202 Accepted):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1683 (Resource Missing)` | 404 | "The `id` provided is not found..." | | `1658 (Not Acceptable)` | 406 | "The session `id` provided is not in a valid state to assemble." | | `1658 (Not Acceptable)` | 406 | "No chunks have been uploaded to this session." | | `1658 (Not Acceptable)` | 406 | "The chunks provided do not match the size of the file." | | `1685 (Feature Limit)` | 403 | "You have created too many upload sessions..." | | `1678 (Enqueue Failed)` | 500 | "Your request was valid but could not be processed." | ### Step 4: Poll for completion ``` GET /current/upload/{upload_id}/details/?wait=60 Authorization: Bearer {jwt_token} ``` **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The upload session ID | **Query parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `wait` | integer | No | - | Long-poll wait time in seconds (1 to 590). Server holds the connection and returns immediately when the status changes. | The server detects status changes efficiently during long-poll. Maximum wait is 590 seconds. **curl example:** ```bash curl -X GET "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/details/?wait=60" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "session": { "id": "u1abc-defgh-ijklm-nopqr-stuvw-xyz123", "name": "annual-report.pdf", "size": 52428800, "status": "stored", "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hash_algo": "sha256", "created": "2025-01-20 10:30:00", "updated": "2025-01-20 10:35:00", "chunks": { "1": 5242880, "2": 5242880, "3": 5242880, "4": 5242880, "5": 5242880 } } }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.session.id` | string | Upload session OpaqueId | | `response.session.name` | string | Filename | | `response.session.size` | integer | Declared file size in bytes | | `response.session.status` | string | Current status (see status table above) | | `response.session.hash` | string | File hash (if provided) | | `response.session.hash_algo` | string | Hash algorithm (if provided) | | `response.session.created` | string | Session creation timestamp | | `response.session.updated` | string | Last update timestamp | | `response.session.chunks` | object | Map of chunk order (string key) to chunk size (integer value) | | `response.session.new_file_id` | string | OpaqueId of created storage node (only when `stored` with a target) | **Exit condition:** Stop polling when `status` is `stored`, `assembly_failed`, or `store_failed`. ### Step 5 (if no `instance_id`): Add file to storage manually ``` POST /current/workspace/{workspace_id}/storage/{folder_id}/addfile/ ``` or ``` POST /current/share/{share_id}/storage/{folder_id}/addfile/ ``` **Path parameters:** - `{workspace_id}` or `{share_id}` -- Profile ID (19-digit numeric string) - `{folder_id}` -- OpaqueId of the target folder, or `"root"` for the storage root **Body parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `from` | string (JSON) | Yes | Source specification as JSON-encoded string | **`from` format:** ``` from={"type":"upload","upload":{"id":"{upload_id}"}} ``` The value must be a JSON string sent as a form field: ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/root/addfile/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'from={"type":"upload","upload":{"id":"u1abc-defgh-ijklm-nopqr-stuvw-xyz123"}}' ``` ### Step 6 (optional): Clean up session ``` DELETE /current/upload/{upload_id} Authorization: Bearer {jwt_token} ``` --- ## Complete Chunked Upload Example **1. Create session (25 MB file, 5 chunks):** ```bash curl -X POST "https://api.fast.io/current/upload/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=presentation.pptx" \ -d "size=26214400" \ -d "action=create" \ -d "instance_id=12345678901234567890" \ -d "folder_id=root" ``` Response: ```json {"result": "yes", "response": {"id": "u1abc-defgh-ijklm-nopqr-stuvw-xyz123"}, "current_api_version": "1.0"} ``` **2. Upload 5 chunks (3 in parallel, then 2 more):** ```bash # Chunks 1-3 in parallel curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/chunk/?order=1&size=5242880" \ -H "Authorization: Bearer {jwt_token}" -F "chunk=@chunk1.bin" & curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/chunk/?order=2&size=5242880" \ -H "Authorization: Bearer {jwt_token}" -F "chunk=@chunk2.bin" & curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/chunk/?order=3&size=5242880" \ -H "Authorization: Bearer {jwt_token}" -F "chunk=@chunk3.bin" & wait # Chunks 4-5 curl -X POST ".../chunk/?order=4&size=5242880" -H "Authorization: Bearer {jwt_token}" -F "chunk=@chunk4.bin" & curl -X POST ".../chunk/?order=5&size=5242880" -H "Authorization: Bearer {jwt_token}" -F "chunk=@chunk5.bin" & wait ``` **3. Trigger assembly:** ```bash curl -X POST "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/complete/" \ -H "Authorization: Bearer {jwt_token}" ``` **4. Poll until stored:** ```bash curl -X GET "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/details/?wait=60" \ -H "Authorization: Bearer {jwt_token}" ``` Response: `{"result": "yes", "response": {"session": {"status": "assembling", ...}}}` -- keep polling ```bash curl -X GET "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123/details/?wait=60" \ -H "Authorization: Bearer {jwt_token}" ``` Response: `{"result": "yes", "response": {"session": {"status": "stored", "new_file_id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", ...}}}` -- done **5. Clean up session:** ```bash curl -X DELETE "https://api.fast.io/current/upload/u1abc-defgh-ijklm-nopqr-stuvw-xyz123" \ -H "Authorization: Bearer {jwt_token}" ``` --- ## Workflow: Stream Upload (Unknown File Size) For clients that don't know the exact file size upfront (piped output, generated content, compressed streams). The client declares a maximum size ceiling, streams the file in a single request, and the system records actual bytes. ### Step 1: Create Stream Session ```bash POST /current/upload/ ``` | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | Filename | | `stream` | string | Yes | Must be `"true"` | | `max_size` | integer | No | Maximum file size in bytes (defaults to plan limit) | | `action` | string | No | `"create"` or `"update"` (same as standard upload) | | `instance_id` | string | Conditional | Target workspace/share ID (required if action=create) | | `file_id` | string | Conditional | File to update (required if action=update) | | `hash` | string | No | Expected whole-file hash | | `hash_algo` | string | No | Hash algorithm | | `creator` | string | No | Client identifier | **Example:** ```bash curl -X POST "https://api.fast.io/current/upload/" \ -H "Authorization: Bearer $TOKEN" \ -d "name=output.tar.gz" \ -d "stream=true" \ -d "max_size=52428800" \ -d "action=create" \ -d "instance_id=12345678901234567890" ``` **Response:** `201 Created` -- returns session `id` for use in step 2. ### Step 2: Stream File Body ```bash POST /current/upload/{upload_id}/stream/ Content-Type: application/octet-stream ``` Send the raw file bytes as the request body. No `size` or `order` parameters needed. | Parameter | Type | Required | Description | |---|---|---|---| | `hash` | string | No | Whole-file hash for validation | | `hash_algo` | string | No | Hash algorithm | **Example:** ```bash curl -X POST "https://api.fast.io/current/upload/$SESSION_ID/stream/" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/octet-stream" \ --data-binary @myfile.tar.gz ``` **Response:** `201 Created` -- the session auto-finalizes. The session's `size` is updated to actual bytes received. ### Notes - The `max_size` parameter is used for quota validation at session creation. If omitted, defaults to the plan's maximum file size. - The actual uploaded bytes must not exceed `max_size`. - Stream mode sessions produce exactly one chunk -- no assembly pipeline is needed. - If you know the exact file size, you can still provide `size` instead of `max_size` (or both). - Stream upload is a single-shot operation: you cannot stream to the same session twice. - Stream mode sessions cannot use the chunk endpoint -- attempting to upload chunks to a stream session will return an error. - Only one concurrent stream upload is allowed per session; concurrent requests to the same session are rejected. --- ## Resume a Disconnected Upload If an upload is interrupted (network failure, client crash), resume it without re-uploading completed chunks. ### Steps: 1. **Get session status:** ```bash curl -X GET "https://api.fast.io/current/upload/{upload_id}/details/" \ -H "Authorization: Bearer {jwt_token}" ``` 2. **Read the `chunks` map** in the response. Keys are chunk numbers already uploaded, values are byte sizes. 3. **Upload only missing chunks.** Compare the `chunks` map against the expected chunk list. Upload any chunks not present. 4. **Trigger assembly:** ```bash curl -X POST "https://api.fast.io/current/upload/{upload_id}/complete/" \ -H "Authorization: Bearer {jwt_token}" ``` 5. **Poll for completion** as normal. --- ## File Update (New Version) To upload a new version of an existing file: 1. **Create session** with `action=update`, `instance_id`, and `file_id` (OpaqueId of the file to replace). 2. Upload chunks and complete as normal. The existing file receives a new version. ```bash curl -X POST "https://api.fast.io/current/upload/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=report-v2.pdf" \ -d "size=1024" \ -d "action=update" \ -d "instance_id=12345678901234567890" \ -d "file_id=f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4" \ -F "chunk=@report-v2.pdf" ``` --- ## Web Upload (URL Import) Import files from any HTTP/HTTPS URL. Supports OAuth-protected URLs (Google Drive, OneDrive, Dropbox, Box, iCloud). The server downloads the file in the background and streams it through the standard upload pipeline. ### Create web upload job ``` POST /current/web_upload/ Content-Type: application/x-www-form-urlencoded Authorization: Bearer {jwt_token} ``` **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `source_url` | string | Yes | URL to download the file from. Only HTTP/HTTPS supported. Max 2048 characters. | | `file_name` | string | Yes | Filename to save as (1-255 chars) | | `profile_id` | string | Yes | Target workspace or share profile ID (19-digit numeric) | | `profile_type` | string | Yes | `"workspace"` or `"share"` | | `folder_id` | string | No | Target folder OpaqueId or `"root"` for storage root | | `relative_path` | string | No | Relative path for automatic folder creation (1-8192 chars) | | `options` | integer | No | Bitfield options (default: 0). See options table. | | `creator` | string | No | Client identifier string (1-150 chars, alphanumeric and hyphens only) | **Options bitfield:** | Bit | Value | Description | |---|---|---| | 0 | `0x1` | Overwrite if a file with the same name exists at the destination | | 1 | `0x2` | Skip virus scanning (admin only) | | 2 | `0x4` | Attempt to preserve source file metadata | **curl example:** ```bash curl -X POST "https://api.fast.io/current/web_upload/" \ -H "Authorization: Bearer {jwt_token}" \ -d "source_url=https://example.com/files/document.pdf" \ -d "file_name=document.pdf" \ -d "profile_id=12345678901234567890" \ -d "profile_type=workspace" \ -d "folder_id=root" ``` **Response (201 Created):** ```json { "result": "yes", "response": { "web_upload": { "id": "aBcDeFgHiJkLmNoPqRsT123456", "user_id": "12345678901234567890", "profile_id": "12345678901234567890", "profile_type": "workspace", "source_url": "https://example.com/files/document.pdf", "file_name": "document.pdf", "folder_id": null, "relative_path": null, "status": "queued", "bytes_downloaded": 0, "expected_size": null, "upload_session_id": null, "async_job_id": null, "status_description": "Queued for processing", "creator": "12345678901234567890", "error_message": null, "options": 0, "properties": null, "created": "2025-01-26 15:00:00", "updated": "2025-01-26 15:00:00" } }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.web_upload.id` | string | Web upload job OpaqueId | | `response.web_upload.user_id` | string | ID of the user who created the job | | `response.web_upload.profile_id` | string | Target workspace or share ID | | `response.web_upload.profile_type` | string | `"workspace"` or `"share"` | | `response.web_upload.source_url` | string | The source URL | | `response.web_upload.file_name` | string | Destination filename | | `response.web_upload.folder_id` | string/null | Target folder OpaqueId | | `response.web_upload.relative_path` | string/null | Relative path for folder creation | | `response.web_upload.status` | string | Status string (e.g., `"queued"`, `"downloading"`, `"complete"`) | | `response.web_upload.bytes_downloaded` | integer | Bytes downloaded so far (0 initially) | | `response.web_upload.expected_size` | integer/null | Expected file size (from HEAD request, if known) | | `response.web_upload.upload_session_id` | string/null | Linked upload session ID (populated during uploading phase) | | `response.web_upload.async_job_id` | string/null | The async job ID processing this upload | | `response.web_upload.status_description` | string | Human-readable status description | | `response.web_upload.creator` | string | User ID of the creator | | `response.web_upload.error_message` | string/null | Error details if failed | | `response.web_upload.options` | integer | Options bitfield | | `response.web_upload.created` | string | Creation timestamp | | `response.web_upload.updated` | string | Last update timestamp | **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1605 (Invalid Input)` | 400 | "Invalid URL. Only HTTP and HTTPS URLs are supported." | | `1605 (Invalid Input)` | 400 | "Invalid profile_type. Must be \"workspace\" or \"share\"." | | `1680 (Access Denied)` | 403 | "You do not have permission to upload to this workspace." | | `1680 (Access Denied)` | 403 | "You do not have permission to upload to this share." | | `1658 (Not Acceptable)` | 406 | "You have too many active web uploads..." | | `1654 (Internal Error)` | 500 | "Failed to create web upload job." | **OAuth for protected URLs:** For Google Drive, OneDrive, and other OAuth-protected files, include the access token as a query parameter in the source URL: ``` https://www.googleapis.com/drive/v3/files/{fileId}?alt=media&access_token={oauth_token} ``` The server extracts the token from the URL, removes it from the query string, and sends it as an `Authorization: Bearer` header on all HTTP requests. Tokens are never logged or returned in API responses. ### List web upload jobs ``` GET /current/web_upload/ Authorization: Bearer {jwt_token} ``` **Query parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `limit` | integer | No | 50 | Maximum number of results (1-100) | | `offset` | integer | No | 0 | Pagination offset | | `status` | string | No | - | Filter by status: `"pending"`, `"queued"`, `"downloading"`, `"uploading"`, `"complete"`, `"failed"`, `"canceled"` | **curl example:** ```bash curl -X GET "https://api.fast.io/current/web_upload/?status=downloading&limit=20" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "web_uploads": [ { "id": "aBcDeFgHiJkLmNoPqRsT123456", "user_id": "12345678901234567890", "profile_id": "12345678901234567890", "profile_type": "workspace", "source_url": "https://example.com/files/document.pdf", "file_name": "document.pdf", "folder_id": null, "relative_path": null, "status": "downloading", "status_description": "Downloading file from URL", "bytes_downloaded": 5242880, "expected_size": 52428800, "progress_percent": 10, "upload_session_id": null, "error_message": null, "created": "2025-01-26 15:00:00", "updated": "2025-01-26 15:01:00" } ], "total": 1, "limit": 20, "offset": 0 }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.web_uploads` | array | Array of web upload job objects | | `response.web_uploads[].id` | string | Web upload job OpaqueId | | `response.web_uploads[].profile_type` | string | `"workspace"` or `"share"` | | `response.web_uploads[].status` | string | Status string (e.g., `"downloading"`, `"complete"`) | | `response.web_uploads[].status_description` | string | Human-readable status description | | `response.web_uploads[].bytes_downloaded` | integer | Bytes downloaded so far | | `response.web_uploads[].expected_size` | integer/null | Expected file size (null if unknown) | | `response.web_uploads[].progress_percent` | integer | Download progress percentage (0-100) | | `response.web_uploads[].upload_session_id` | string/null | Linked upload session ID | | `response.web_uploads[].error_message` | string/null | Error details if failed | | `response.total` | integer | Total count of matching records | | `response.limit` | integer | Applied limit | | `response.offset` | integer | Applied offset | ### Get web upload job details ``` GET /current/web_upload/{upload_id}/details/ Authorization: Bearer {jwt_token} ``` **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The web upload job OpaqueId | **curl example:** ```bash curl -X GET "https://api.fast.io/current/web_upload/aBcDeFgHiJkLmNoPqRsT123456/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "web_upload": { "id": "aBcDeFgHiJkLmNoPqRsT123456", "user_id": "12345678901234567890", "profile_id": "12345678901234567890", "profile_type": "workspace", "source_url": "https://example.com/files/document.pdf", "file_name": "document.pdf", "status": "complete", "bytes_downloaded": 52428800, "expected_size": 52428800, "upload_session_id": "xYzAbCdEfGhIjKlMnOpQ123456", "async_job_id": "aBcDeFgHiJkLmNoPqRsT789012", "status_description": "Upload successfully completed", "creator": "12345678901234567890", "error_message": null, "options": 0, "created": "2025-01-26 15:00:00", "updated": "2025-01-26 15:02:00" } }, "current_api_version": "1.0" } ``` Note: Both the details and list endpoints return status as string values (e.g., `"pending"`, `"queued"`, `"downloading"`, `"uploading"`, `"complete"`, `"failed"`, `"canceled"`). **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1609 (Not Found)` | 404 | "Web upload job not found." | | `1680 (Access Denied)` | 403 | "You do not have permission to view this web upload job." | ### Cancel web upload job ``` DELETE /current/web_upload/?id={web_upload_id} Authorization: Bearer {jwt_token} ``` **Query parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `id` | string | Yes | The web upload job OpaqueId to cancel | **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/web_upload/?id=aBcDeFgHiJkLmNoPqRsT123456" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "canceled": true, "id": "aBcDeFgHiJkLmNoPqRsT123456" }, "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1609 (Not Found)` | 404 | "Web upload job not found." | | `1680 (Access Denied)` | 403 | "You do not have permission to cancel this web upload job." | | `1658 (Not Acceptable)` | 406 | "This web upload job cannot be canceled because it is already in a terminal state." | | `1654 (Internal Error)` | 500 | "Failed to cancel web upload job." | ### Web Upload Status Values | Status | Value | Description | Terminal | |---|---|---|---| | `pending` | 1 | Job created, waiting for async job pickup | No | | `queued` | 2 | Async job has been queued for processing | No | | `downloading` | 3 | Actively downloading from the source URL | No | | `uploading` | 4 | Feeding downloaded chunks to upload system | No | | `complete` | 5 | Upload successfully completed | Yes | | `failed` | 6 | Download or upload failed | Yes | | `canceled` | 7 | User canceled the web upload | Yes | ### Web Upload Limits | Limit | Value | |---|---| | Max active per user | 50 (non-terminal jobs) | | Max file size | Up to 40 GB (subject to plan limits) | | Max retries | 3 (automatic on transient failures) | | Retry delay | 60 seconds between attempts | --- ## Upload Management Endpoints ### List all upload sessions ``` GET /current/upload/details/ Authorization: Bearer {jwt_token} ``` Returns all upload sessions for the current user in any state. **curl example:** ```bash curl -X GET "https://api.fast.io/current/upload/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "results": 2, "sessions": [ { "id": "aBcDeFgHiJkLmNoPqRsT123456", "name": "document.pdf", "size": 52428800, "status": "uploading", "hash": "e3b0c44298fc1c14...", "hash_algo": "sha256", "created": "2025-01-20 10:30:00", "updated": "2025-01-20 10:35:00" } ] }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.results` | integer | Total number of sessions (only present when > 1) | | `response.sessions` | array | Array of upload session objects | ### Delete/cancel an upload session ``` DELETE /current/upload/{upload_id} Authorization: Bearer {jwt_token} ``` **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The upload session ID to delete (appended to URL path) | Cancel and delete an active upload session. Cleans up temporary chunk files and releases session quota. If the session has an associated web upload job, that job is automatically canceled. Sessions can be deleted in states: `ready`, `uploading`, `assembly_failed`, `store_failed`, `complete`. Sessions in `assemble`, `assembling`, `store`, `storing`, or `stored` states cannot be deleted. **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/upload/aBcDeFgHiJkLmNoPqRsT123456" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1605 (Invalid Input)` | 400 | "The `id` provided is not found or is not associated with your account." | | `1658 (Not Acceptable)` | 406 | "The session `id` provided is not in a valid state to delete." | | `1654 (Internal Error)` | 500 | "We were unable to delete the requested upload session." | ### Get upload limits ``` GET /current/upload/limits/ Authorization: Bearer {jwt_token} ``` Returns upload limits based on the user's billing plan and the target context. **Query parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `action` | string | No | - | `"create"` or `"update"` to get limits in context of a target | | `org` | string | No | - | Organization ID for limit resolution (used when no `action` specified) | | `instance_id` | string | Required if action=create | - | Target workspace or share ID | | `folder_id` | string | No | - | Target folder OpaqueId or `"root"` | | `file_id` | string | Required if action=update | - | File ID for update context | **curl example:** ```bash # General limits (with org context) curl -X GET "https://api.fast.io/current/upload/limits/?org=12345678901234567890" \ -H "Authorization: Bearer {jwt_token}" # Limits for creating a file in a workspace curl -X GET "https://api.fast.io/current/upload/limits/?action=create&instance_id=12345678901234567890" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "limits": { "chunk_size": 104857600, "size": 42949672960, "chunks": 500, "sessions": 7500, "sessions_size_max": 214748364800 } }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.limits.chunk_size` | integer | Maximum size of a single chunk in bytes | | `response.limits.size` | integer | Maximum total file size in bytes | | `response.limits.chunks` | integer | Maximum number of chunks per upload session | | `response.limits.sessions` | integer | Maximum concurrent active upload sessions | | `response.limits.sessions_size_max` | integer | Maximum aggregate size of all active sessions in bytes | ### Get restricted file extensions ``` GET /current/upload/limits/extensions/ ``` Returns restricted and archive file extensions. **Authentication is optional** -- unauthenticated users receive free plan restrictions. **Query parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `plan` | string | No | User's plan or `"free"` | Override the billing plan to check restrictions for | **curl example:** ```bash curl -X GET "https://api.fast.io/current/upload/limits/extensions/" # With specific plan curl -X GET "https://api.fast.io/current/upload/limits/extensions/?plan=pro" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "restricted_extensions": [".exe", ".apk", ".jar", ".php"], "archive_extensions": [".7z", ".zip", ".rar", ".tar.gz", ".bz2"], "enforcement_enabled": true, "plan": "free", "cache_ttl": 86400 }, "current_api_version": "1.0" } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `response.restricted_extensions` | string[] | Extensions blocked for the plan | | `response.archive_extensions` | string[] | Archive extensions (only populated if the plan restricts archives) | | `response.enforcement_enabled` | boolean | Whether extension restriction enforcement is currently active | | `response.plan` | string | The plan used for this response | | `response.cache_ttl` | integer | Suggested client-side cache TTL in seconds (86400 = 24 hours) | Clients should call this once on startup and cache the results for 24 hours. ### List supported hash algorithms ``` GET /current/upload/algos/ Authorization: Bearer {jwt_token} ``` **curl example:** ```bash curl -X GET "https://api.fast.io/current/upload/algos/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "algos": ["md5", "sha1", "sha256", "sha384"] }, "current_api_version": "1.0" } ``` ### Get chunk information ``` GET /current/upload/{upload_id}/chunk/ Authorization: Bearer {jwt_token} ``` Returns information about all uploaded chunks for a session. **Path parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `{upload_id}` | string | Yes | The upload session ID | To retrieve a specific chunk, append the chunk number to the path: ``` GET /current/upload/{upload_id}/chunk/{order} ``` | Parameter | Type | Required | Description | |---|---|---|---| | `{order}` | integer | No | Specific chunk number to retrieve. If omitted, returns all chunks. | **curl example (all chunks):** ```bash curl -X GET "https://api.fast.io/current/upload/aBcDeFgHiJkLmNoPqRsT123456/chunk/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK, all chunks):** ```json { "result": "yes", "response": { "chunks": { "1": 5242880, "2": 5242880, "3": 2097152 } }, "current_api_version": "1.0" } ``` **curl example (single chunk):** ```bash curl -X GET "https://api.fast.io/current/upload/aBcDeFgHiJkLmNoPqRsT123456/chunk/1" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK, single chunk):** ```json { "result": "yes", "response": { "chunk": { "1": 5242880 } }, "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1609 (Not Found)` | 404 | "The supplied chunk not valid or found." | ### Delete a chunk ``` DELETE /current/upload/{upload_id}/chunk/?order={n} Authorization: Bearer {jwt_token} ``` Delete a specific chunk from an upload session. Session must be in `uploading` state. **Query parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `order` | integer | Yes | The chunk number to delete | **curl example:** ```bash curl -X DELETE "https://api.fast.io/current/upload/aBcDeFgHiJkLmNoPqRsT123456/chunk/?order=3" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Message | |---|---|---| | `1658 (Not Acceptable)` | 406 | "The session `id` provided is not in a valid state to delete a chunk." | | `1654 (Internal Error)` | 500 | "We were unable to delete the requested upload session chunk." | --- ## Quick Reference ### Small file (one request, auto-add): ``` POST /current/upload/ multipart: name, size, chunk, action=create, instance_id, folder_id -> 201: {id, new_file_id} ``` ### Large file (chunked): ``` POST /current/upload/ # Create session form: name, size, action=create, instance_id, folder_id -> 201: {id} POST /current/upload/{id}/chunk/?order=N&size=N # Upload chunks (up to 3 parallel) multipart: chunk -> 202 POST /current/upload/{id}/complete/ # Trigger assembly -> 202 GET /current/upload/{id}/details/?wait=60 # Poll until stored -> 200: {session: {status, new_file_id}} DELETE /current/upload/{id} # Clean up session -> 200 ``` ### Manual add to storage (if no instance_id): ``` POST /current/workspace/{id}/storage/{folder}/addfile/ form: from={"type":"upload","upload":{"id":"{upload_id}"}} -> 200 ``` ### Web upload (URL import): ``` POST /current/web_upload/ # Create job form: source_url, file_name, profile_id, profile_type -> 201: {web_upload} GET /current/web_upload/ # List jobs query: limit, offset, status -> 200: {web_uploads, total} GET /current/web_upload/{id}/details/ # Get job details -> 200: {web_upload} DELETE /current/web_upload/?id={id} # Cancel job -> 200: {canceled, id} ``` ### Upload management: ``` GET /current/upload/details/ # List all sessions GET /current/upload/limits/ # Get plan limits GET /current/upload/limits/extensions/ # Get restricted extensions GET /current/upload/algos/ # List hash algorithms GET /current/upload/{id}/chunk/ # Get all chunk info GET /current/upload/{id}/chunk/{order} # Get single chunk info DELETE /current/upload/{id}/chunk/?order=N # Delete a chunk ``` --- ## Best Practices - **Check limits first**: Query `/upload/limits/` and `/upload/limits/extensions/` before starting uploads. - **Use hash validation**: Always provide chunk and file hashes to detect corruption early. - **Implement retry logic**: Failed chunk uploads can be retried by re-uploading the same `order`. - **Track chunks locally**: Maintain a local record of successfully uploaded chunks for resumability. - **Long-poll for completion**: Use the `wait` parameter on the details endpoint instead of frequent polling. - **Clean up failures**: DELETE failed sessions to free session quota. - **Cache extension restrictions**: Call `/upload/limits/extensions/` once and cache for 24 hours. - **Use auto-finalization**: When all chunks total the declared file size, assembly triggers automatically. Explicit `/complete/` is optional but recommended for reliability. - **Omit `relative_path` when unused**: Do NOT send it as an empty string. > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # AI, Chat & Metadata Base URL: `https://api.fast.io/current/` All endpoints require authentication unless noted: `Authorization: Bearer {jwt_token}` --- ## Overview Fast.io provides built-in AI capabilities for workspaces and shares: - **RAG-powered chat** -- Ask questions about indexed files with citations to specific pages and snippets - **General chat** -- AI conversation with optional file attachments for one-off analysis - **Auto-summarization** -- AI-generated titles and descriptions for shares - **Metadata extraction** -- AI-powered structured metadata extraction from documents, spreadsheets, images, and code - **Semantic search** -- Find files by meaning, not just keywords - **Notes** -- Markdown storage nodes that are indexed for RAG, letting you bank knowledge over time AI chat is **read-only**. It can read, analyze, search, and answer questions about files, but it cannot modify files, change settings, manage members, or access events. All write operations must be done through the API directly. --- ## Intelligence Setting The `intelligence` boolean on a workspace or portal share controls whether uploaded files are automatically indexed for RAG. - **`intelligence=true`** (default for agent accounts) -- Files are auto-indexed for semantic search, summarization, and citation. Required for `chat_with_files` type chats with file/folder scope. - **`intelligence=false`** -- Files are stored/shared without RAG indexing. You can still use `chat` type with `files_attach` for direct file analysis. **Shared folder restriction:** Intelligence is only available on portal shares (independent storage). Workspace folder shares (`storage_mode=workspace_folder`) cannot have intelligence enabled — their files are indexed through the parent workspace instead. The API will return an error if you attempt to enable intelligence on a shared folder share. **Enable at creation:** ``` POST /current/org/{org_id}/create/workspace/ intelligence=true ``` **Update later:** ``` POST /current/workspace/{workspace_id}/update/ intelligence=true ``` **Note:** Intelligence can be enabled and disabled within time restrictions. Disabling intelligence destroys indexed embeddings (the vector index is flushed). Re-enabling intelligence incurs re-indexing costs as AI credits are consumed to re-index all files. --- ## Chat Types Two chat types are available. Both augment answers with web knowledge. ### `chat` -- General AI Conversation - Does **not** use RAG or workspace file indexing - Works regardless of the workspace `intelligence` setting - You can attach files directly via `files_attach` for one-off analysis (e.g., "Describe this image", "Summarize this PDF") - Any file with a ready preview or AI summary can be attached ### `chat_with_files` -- RAG-Powered Chat - Searches indexed files in the workspace/share (or a scoped folder/file set) - Returns answers with **citations** to specific files, pages, and text snippets - Requires the workspace/share `intelligence` setting to be enabled when using file/folder scope - Default scope is the **entire workspace/share** -- omit scope parameters to search all indexed files - Only files with `ai_state: ready` are included in searches --- ## File Scope vs File Attachments These are two different ways to give the AI context. **They cannot be combined in the same request.** ### Folder/File Scope (`files_scope`, `folders_scope`) Limits the RAG search space. The AI searches indexed files within the specified scope and uses matching content as citations. - **Default scope is the entire workspace/share** -- omit both `files_scope` and `folders_scope` to search all indexed files - Only files with `ai_state: ready` are included - Only used with `chat_with_files` type **`files_scope`** -- comma-separated `nodeId:versionId` pairs (max 100) Both `nodeId` and `versionId` are **required and must be non-empty** in each pair. Get the `versionId` from the file's `version` field in storage list/details responses. Example: `files_scope=f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4:v1abc-defgh-ijklm,f4abc-defgh-ijklm-nopqr-stuvw-xyz456:v2xyz-opqrs-tuvwx` **`folders_scope`** -- comma-separated `nodeId:depth` pairs (max 100 subfolder references) Depth controls how many levels of subfolders are enumerated (1-10). Only subfolder references count toward the limit, not individual files. The RAG backend searches all indexed files within the scoped folders automatically. Example: `folders_scope=d5abc-defgh-ijklm-nopqr-stuvw-xyz789:3` You do **not** need to enumerate individual files. Just provide top-level folder IDs with depth. ### File Attachments (`files_attach`) Files are directly attached to the chat message for analysis. Use this for direct analysis tasks. - Files must have AI summaries ready (be in a "ready" state or eligible for summarization) - Max 20 files, 200 MB total (effective size after preview optimization) - Works with both `chat` and `chat_with_files` types - Good for: "Describe this image", "Extract action items from this transcript", "Compare these two contracts" Format: comma-separated `nodeId:versionId` pairs, e.g., `files_attach=nodeId1:versionId1,nodeId2:versionId2` **Important:** Only **file** nodeIds are accepted — passing a **folder** nodeId will be rejected. To include folder contents in AI context, use `folders_scope` instead. **Note:** If a referenced node has been deleted, trashed, or is otherwise inaccessible, it is silently dropped and the request continues with the remaining valid references. This applies to `files_scope`, `folders_scope`, and `files_attach`. ### Choosing Between Scope and Attachments | Use Case | Mechanism | Chat Type | |---|---|---| | Search across many indexed files | `files_scope` / `folders_scope` | `chat_with_files` | | Analyze specific files directly | `files_attach` | `chat` or `chat_with_files` | | Ask general questions about the workspace | Omit scope (defaults to all files) | `chat_with_files` | | General conversation, no files | Neither | `chat` | --- ## AI State (File Readiness) Files in an intelligent workspace progress through AI processing states: | State | Meaning | |---|---| | `disabled` | Intelligence not enabled for this file/workspace | | `pending` | Queued for AI processing | | `inprogress` | Currently being processed by AI | | `ready` | File can be used with AI chat (attached directly or via scope). The file has been processed enough (e.g., preview/summary generated) to be usable in AI conversations. | | `indexed` | File contents (for documents) have been indexed via RAG. This state is used when intelligence is enabled on the workspace/share. Indexed files are searchable by semantic meaning and their content is used as grounding in scoped AI chats. | | `failed` | AI processing failed | Files with `ai_state: ready` can be used with AI chat. Files with `ai_state: indexed` have additionally had their contents indexed for RAG-powered semantic search. When intelligence is enabled on a workspace/share, files progress to `indexed` automatically. Check a file's AI state in the `ai.state` field of storage list or file details responses. --- ## Personality Parameter The `personality` parameter controls the length and style of AI responses. Pass it when creating a chat or sending a message. | Value | Behavior | |---|---| | `concise` | Short, brief, direct answers with minimal elaboration | | `detailed` | Comprehensive answers with context, evidence, and examples (default) | You can also control verbosity directly in your question phrasing: - "In one sentence, what is the main conclusion?" - "List only the file names that mention GDPR, no explanations" - "Give me a brief summary -- 2-3 bullet points max" --- ## Notes (Stored Knowledge) Notes are a storage node type (like files and folders) that store markdown content directly on the server. They appear in storage listings with `"type": "note"` and `"mimetype": "text/markdown"`. Notes are **workspace-only** -- they cannot be created in shares. ### Why Notes Matter In an intelligent workspace, notes are ingested and indexed just like uploaded files. This makes them a way to **bank knowledge over time** -- store interesting facts, research findings, decisions, or project context. In future AI chats that scope the entire workspace (or include the note's folder), the note content will be used as grounding when the AI searches for relevant information. ### Create a note ``` POST /current/workspace/{workspace_id}/storage/{parent_id}/createnote/ ``` | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | Note name, must end in `.md` | | `content` | string | Yes | Markdown content, max 100 KB | `{parent_id}` is a folder OpaqueId or `"root"`. Returns the created note as a node resource. ### Update a note ``` POST /current/workspace/{workspace_id}/storage/{node_id}/updatenote/ ``` | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | No | New name, must end in `.md` | | `content` | string | No | New markdown content, max 100 KB | At least one of `name` or `content` must be provided. Updating content creates a new version. ### Read note content ``` GET /current/workspace/{workspace_id}/storage/{node_id}/read/ ``` Returns the raw markdown content. ### Linking a user to a note - In workspace context: `https://{org_domain}.fast.io/workspace/{workspace_name}?note={note_opaque_id}` - Direct preview: use the preview URL for the note node --- ## Workspace AI Endpoints --- ### Create a new chat ``` POST /current/workspace/{workspace_id}/ai/chat/ ``` Creates a chat with an initial message. The AI begins processing asynchronously. **Auth:** Bearer token required. Workspace `view` permission. `content_ai` plan feature required. **Parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `type` | string | Yes | -- | `chat` or `chat_with_files` | | `question` | string | Yes | -- | Initial question, 2-12,768 characters | | `privacy` | string | No | `public` | `private` or `public` | | `name` | string | No | Auto-generated | Chat name. Auto-generated from first 10 words of question if omitted. | | `personality` | string | No | `detailed` | `concise` or `detailed` | | `files_scope` | string | No | All indexed files | Comma-separated `nodeId:versionId` pairs (max 100). Requires `chat_with_files` type and Intelligence enabled. | | `folders_scope` | string | No | All indexed files | Comma-separated `nodeId:depth` pairs (max 100, depth 1-10). Requires `chat_with_files` type and Intelligence enabled. | | `files_attach` | string | No | -- | Comma-separated **file** `nodeId:versionId` pairs (max 20, 200 MB total). Only file nodes accepted — folder nodes are rejected. Cannot be combined with `files_scope`/`folders_scope`. Files must have AI summaries ready. | **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/" \ -H "Authorization: Bearer {jwt_token}" \ -d "type=chat_with_files" \ -d "question=What were the Q3 revenue figures?" \ -d "privacy=private" \ -d "personality=detailed" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "chat": { "id": "aBcDeFgHiJkLmNoPqR", "message": { "id": "xYzAbCdEfGhIjKlMnO" } } }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.chat.id` | string | Opaque ID of the created chat | | `response.chat.message.id` | string/null | Opaque ID of the initial message, or `null` if no question was provided | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Invalid `type`, `privacy`, or `personality` value | | `1605 (Invalid Input)` | 400 | File/folder scope used without Intelligence enabled | | `1605 (Invalid Input)` | 400 | Both `files_attach` and `files_scope` specified | | `1605 (Invalid Input)` | 400 | Invalid name or other property | | `1664 (Datastore Error)` | 500 | Chat or message creation failed | --- ### List chats ``` GET /current/workspace/{workspace_id}/ai/chat/list/ ``` Returns all chats created by the current user in the workspace. Sorted by most recently modified first. **Auth:** Bearer token required. Workspace `view` permission. `content_ai` plan feature required. **Variant:** Append `/deleted` to the path to list deleted chats: `GET .../ai/chat/list/deleted` **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "chats": [ { "chat_id": "aBcDeFgHiJkLmNoPqR", "creator": { "type": "user", "id": "12345678901234567890" }, "type": "chat_with_files", "name": "Quarterly report analysis", "status": "active", "message_count": 5, "latest_message": { "message_id": "xYzAbCdEfGhIjKlMnO", "creator": { "type": "user", "id": "12345678901234567890" }, "state": "complete", "message": { "text": "Summarize the quarterly report" }, "response": null, "created": "2025-06-15T10:30:00Z", "updated": "2025-06-15T10:30:05Z" }, "unique_creators": [ { "type": "user", "id": "12345678901234567890" } ], "cost": { "credits": { "tokens": 1500 } }, "efficiency": "good", "privacy": { "visibility": "private", "owner": { "type": "user", "id": "12345678901234567890" } }, "created_at": "2025-06-15T10:00:00Z", "updated_at": "2025-06-15T10:30:05Z" } ] }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.chats` | array | Array of chat objects | | `response.chats[].chat_id` | string | Opaque ID of the chat | | `response.chats[].creator` | object | `{type, id}` -- the chat creator | | `response.chats[].type` | string | `chat` or `chat_with_files` | | `response.chats[].name` | string | Chat display name | | `response.chats[].status` | string | Chat status | | `response.chats[].message_count` | integer | Total messages in the chat | | `response.chats[].latest_message` | object | Most recent message preview | | `response.chats[].unique_creators` | array | Distinct participants | | `response.chats[].cost.credits.tokens` | integer | Total token cost | | `response.chats[].efficiency` | string | Efficiency rating | | `response.chats[].privacy` | object | `{visibility, owner}` | | `response.chats[].created_at` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.chats[].updated_at` | string | Last update timestamp (`YYYY-MM-DD HH:MM:SS`) | --- ### Get chat details ``` GET /current/workspace/{workspace_id}/ai/chat/{chat_id}/details/ ``` Returns chat details with full message history. **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "chat": { "chat_id": "aBcDeFgHiJkLmNoPqR", "creator": { "type": "user", "id": "12345678901234567890" }, "type": "chat_with_files", "name": "Quarterly report analysis", "status": "active", "message_count": 3, "messages": [ { "chat_id": "aBcDeFgHiJkLmNoPqR", "message_id": "xYzAbCdEfGhIjKlMnO", "creator": { "type": "user", "id": "12345678901234567890" }, "personality": "detailed", "state": "complete", "message": { "text": "Summarize the quarterly report", "author_name": "John", "scope": {}, "attached": [] }, "response": { "text": "The quarterly report shows revenue growth of 15%...", "error": false, "created": "2025-06-15T10:30:05Z", "cost": { "tokens": 500, "details": {} }, "author_name": "Ripley", "events": [], "table_data": [], "analysis_chunks": [], "citations": [ { "hash": "a1b2c3d4", "nodeId": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "versionId": "v1abc-defgh-ijklm", "entries": [ { "page": 3, "snippet": "Revenue increased by 15% year over year...", "timestamp": null } ] } ] }, "created": "2025-06-15T10:30:00Z", "updated": "2025-06-15T10:30:05Z" } ], "unique_creators": [ { "type": "user", "id": "12345678901234567890" } ], "cost": { "credits": { "tokens": 1500 } }, "efficiency": "good", "privacy": { "visibility": "private", "owner": { "type": "user", "id": "12345678901234567890" } }, "created_at": "2025-06-15T10:00:00Z", "updated_at": "2025-06-15T10:30:05Z" } }, "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1609 (Not Found)` | 404 | Chat ID not found | | `1654 (Internal Error)` | 500 | Internal failure loading chat | --- ### Update a chat ``` POST /current/workspace/{workspace_id}/ai/chat/{chat_id}/update/ ``` Update the name of an existing chat. **Auth:** Bearer token required. `content_ai` plan feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | New chat name | **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=Updated Chat Name" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1605 (Invalid Input)` | 400 | Invalid name value | | `1664 (Datastore Error)` | 500 | Update failed | --- ### Delete a chat ``` DELETE /current/workspace/{workspace_id}/ai/chat/{chat_id}/ ``` **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1654 (Internal Error)` | 500 | Chat in non-deletable state or internal error | Deleted chats can be listed via `GET .../ai/chat/list/deleted`. --- ### Send a follow-up message ``` POST /current/workspace/{workspace_id}/ai/chat/{chat_id}/message/ ``` Send a new message to an existing chat. The message is processed asynchronously. **Auth:** Bearer token required. `content_ai` plan feature required. **Parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `question` | string | Yes | -- | Follow-up question, 2-12,768 characters | | `personality` | string | No | `detailed` | `concise` or `detailed` | | `files_scope` | string | No | All indexed files | Comma-separated `nodeId:versionId` pairs (max 100). Only for `chat_with_files` type. | | `folders_scope` | string | No | All indexed files | Comma-separated `nodeId:depth` pairs (max 100, depth 1-10). Only for `chat_with_files` type. | | `files_attach` | string | No | -- | Comma-separated **file** `nodeId:versionId` pairs (max 20, 200 MB total). Only file nodes accepted — folder nodes are rejected. Cannot be combined with scope params. | The chat type is inherited from the chat itself -- you do not need to specify `type` again. **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/" \ -H "Authorization: Bearer {jwt_token}" \ -d "question=How does that compare to Q2?" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": { "id": "mNoPqRsTuVwXyZaBcD" } }, "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1605 (Invalid Input)` | 400 | Both `files_attach` and `files_scope` specified | | `1605 (Invalid Input)` | 400 | Scope used with wrong chat type | | `1654 (Internal Error)` | 500 | Message creation or queuing failed | --- ### List messages in a chat ``` GET /current/workspace/{workspace_id}/ai/chat/{chat_id}/messages/list/ ``` Returns all messages in chronological order (oldest first). **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/messages/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "messages": [ { "chat_id": "aBcDeFgHiJkLmNoPqR", "message_id": "xYzAbCdEfGhIjKlMnO", "creator": { "type": "user", "id": "12345678901234567890" }, "personality": "detailed", "state": "complete", "message": { "text": "Summarize the quarterly report", "author_name": "John", "scope": {}, "attached": [] }, "response": { "text": "The quarterly report shows...", "error": false, "created": "2025-06-15T10:30:05Z", "cost": { "tokens": 500, "details": {} }, "author_name": "Ripley", "events": [], "table_data": [], "analysis_chunks": [], "citations": [] }, "created": "2025-06-15T10:30:00Z", "updated": "2025-06-15T10:30:05Z" } ] }, "current_api_version": "1.0" } ``` --- ### Get message details ``` GET /current/workspace/{workspace_id}/ai/chat/{chat_id}/message/{message_id}/details/ ``` Retrieve detailed information about a specific message, including response text, citations, and cost. **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Key response fields:** | Field | Description | |---|---| | `state` | Message processing state: `ready`, `in_progress`, `complete`, `errored` | | `message.text` | The original question text | | `message.scope` | File/folder scope used for RAG context | | `message.attached` | Attached files as `{node_id, version_id}` objects | | `response.text` | The AI response (available when `state` is `complete`) | | `response.error` | Whether an error occurred during processing | | `response.cost` | `{tokens, details}` -- message token cost | | `response.citations` | Array of file citations (see Citation Format below) | Only read the response when `state` is `complete`. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Message not found in the chat | | `1654 (Internal Error)` | 500 | Internal retrieval failure | --- ### Stream message response (SSE) ``` GET /current/workspace/{workspace_id}/ai/chat/{chat_id}/message/{message_id}/read/ ``` Returns a Server-Sent Events (SSE) stream of the AI response. **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -N -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/read/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Accept: text/event-stream" ``` **SSE stream format:** ``` event: data data: {"item": "The quarterly report "} event: data data: {"item": "shows revenue growth of "} event: data data: {"item": "15% year over year."} event: analysis_data data: {"type": "analysis_chunk", ...} event: table_data data: {"type": "table_data", ...} event: done ``` **SSE event types:** | Event Type | Description | |---|---| | `data` | Text chunks of the AI response. Payload: `{"item": "..."}`. Concatenate all `data` events for the full text. | | `event` | Status update or event notification | | `analysis_data` | Structured analysis data, citations, and references to source files | | `table_data` | Tabular data extracted or generated by the AI | | `done` | Stream complete. No more events will be sent. | **Behavior:** - The message must be in `in_progress` or `complete` state - For completed messages, all existing chunks are sent immediately followed by `done` - For in-progress messages, the connection stays open and polls for new chunks at intervals (~5 seconds) - The connection auto-terminates after a maximum wait time - Headers include `Cache-Control: no-cache, no-store, must-revalidate` - This endpoint returns an SSE stream, NOT the standard JSON response envelope **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Message not found | | `1605 (Invalid Input)` | 400 | Message not in `in_progress` or `complete` state | | `1683 (Resource Missing)` | 404 | No SSE chunk data exists for this message | | `1654 (Internal Error)` | 500 | Internal streaming failure | --- ### Publish a private chat ``` POST /current/workspace/{workspace_id}/ai/chat/{chat_id}/publish/ ``` Makes a private chat public (visible to other workspace members). One-way operation -- published chats cannot be made private again. **Auth:** Bearer token required. `content_ai` plan feature required. **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/publish/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1660 (Conflict)` | 409 | Chat is already public | | `1664 (Datastore Error)` | 500 | Update failed | --- ### Generate AI Share ``` POST /current/workspace/{workspace_id}/ai/share/ ``` Generates markdown with temporary download URLs for selected files. Designed to be pasted into external AI chatbots (ChatGPT, Claude, etc.) to provide them with file context. **Auth:** Bearer token required. Workspace `view` permission. **Does NOT require** `content_ai` plan feature -- available on all plans. **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `files` | array (JSON) | Yes | JSON array of file opaque IDs. Min 1, max 25. | **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/share/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"files": ["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..." }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.markdown` | string | Generated markdown with file info and temporary download URLs | **Notes:** - Download URLs expire after 5 minutes (300 seconds) - Each token can be used a maximum of 3 times - Individual files limited to 50 MB; total size limited to 100 MB - When more than 5 files: titles only. 5 or fewer: includes full descriptions. - The `files` input is a JSON array (not comma-separated strings) **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Empty files array | | `1605 (Invalid Input)` | 400 | More than 25 files | --- ### List AI transactions ``` GET /current/workspace/{workspace_id}/ai/transactions/ ``` Returns up to 40 most recent AI token usage transactions for the workspace. **Workspace-only** -- no share equivalent. **Auth:** Bearer token required. Workspace `view` permission. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/transactions/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "count": 2, "items": [ { "id": "txn_abc123", "type": "chat_with_files", "status": "complete", "tokens": 1500, "updated": "2025-06-15 10:30:05 UTC", "created": "2025-06-15 10:30:00 UTC" } ] }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.count` | integer | Number of transactions returned | | `response.items[].id` | string | Formatted transaction ID | | `response.items[].type` | string | Parent type (e.g., `chat`, `chat_with_files`) | | `response.items[].status` | string | Transaction status | | `response.items[].tokens` | integer | Token credits consumed | | `response.items[].updated` | string | Last update timestamp (UTC) | | `response.items[].created` | string | Creation timestamp (UTC) | --- ## Asking a Question and Getting the Response ### Complete workflow: **1. Create the chat:** ```bash curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/" \ -H "Authorization: Bearer {jwt_token}" \ -d "question=What were the Q3 revenue figures?" \ -d "type=chat_with_files" \ -d "personality=detailed" ``` Response includes `chat_id` and `message_id`. The AI begins processing asynchronously. **2. Wait for completion using activity polling (do NOT poll the message endpoint in a loop):** ```bash curl -X GET "https://api.fast.io/current/activity/poll/{workspace_id}?wait=95&lastactivity={timestamp}" \ -H "Authorization: Bearer {jwt_token}" ``` Watch for the `ai_chat:{chatId}` activity key. This fires when the message state changes. The server holds the connection for up to 95 seconds and returns immediately when something changes. **3. Check message state:** ```bash curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/{chat_id}/message/{message_id}/details/" \ -H "Authorization: Bearer {jwt_token}" ``` Message states: `ready` -> `in_progress` -> `complete` (or `errored`). Only read the response when state is `complete`. **4. Stream the response:** ```bash curl -N -X GET "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/{chat_id}/message/{message_id}/read/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Accept: text/event-stream" ``` Returns SSE with event types: `data` (text chunks), `analysis_data`, `table_data`, `done`. **5. Send follow-ups:** ```bash curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/{chat_id}/message/" \ -H "Authorization: Bearer {jwt_token}" \ -d "question=How does that compare to Q2?" ``` Same polling flow for the reply. ### Linking a user to an AI chat Construct a workspace URL with a `chat` query parameter: ``` https://{org_domain}.fast.io/workspace/{workspace_name}?chat={chat_opaque_id} ``` The `chat_opaque_id` is returned in the `chat_id` field when creating or listing chats. --- ## Share AI Endpoints Share AI endpoints follow the same pattern as workspace AI. Replace `/workspace/{workspace_id}` with `/share/{share_id}`. ### Share-specific AI endpoints #### Auto-generate OG image ``` GET /current/share/{share_id}/ai/autoog/ ``` Generates an Open Graph image for the share. Returns binary PNG image data (not JSON). **Auth:** Conditional -- required for private shares; optional for public shares. | Behavior | Description | |---|---| | Public share | Custom AI-generated image based on share content | | Private share | Default private image returned | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1609 (Not Found)` | 404 | Share is disabled | | `1654 (Internal Error)` | 500 | Default image not found on server | --- #### Auto-generate title and description ``` POST /current/share/{share_id}/ai/autotitle/ ``` AI-generates a title, description, and display type based on the share's contents. Values are applied directly to the share. **Auth:** Bearer token required. | Parameter | Type | Required | Description | |---|---|---|---| | `context` | string | No | Optional user-provided context to guide AI generation | **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/autotitle/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "title": "Q4 Financial Reports", "description": "Quarterly financial reports and analysis for fiscal year 2025.", "display_type": "document" } } ``` | Field | Type | Description | |---|---|---| | `response.title` | string | AI-generated title | | `response.description` | string | AI-generated description | | `response.display_type` | string | AI-suggested display type | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1680 (Access Denied)` | 403 | Insufficient share permissions | | `1654 (Internal Error)` | 500 | Share update or generation failure | --- ### Share Chat Endpoints Share chat endpoints mirror workspace chat endpoints. All response formats and schemas are identical to the workspace versions documented above. The key differences are: - Auth uses share permissions instead of workspace permissions - File scope references share files instead of workspace files - No AI Transactions endpoint (workspace-only) --- #### Create a new chat (Share) ``` POST /current/share/{share_id}/ai/chat/ ``` Creates a chat with an initial message in a share. The AI begins processing asynchronously. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `type` | string | Yes | -- | `chat` or `chat_with_files` | | `question` | string | Yes | -- | Initial question, 2-12,768 characters | | `privacy` | string | No | `public` | `private` or `public` | | `name` | string | No | Auto-generated | Chat name. Auto-generated from first 10 words of question if omitted. | | `personality` | string | No | `detailed` | `concise` or `detailed` | | `files_scope` | string | No | All indexed files | Comma-separated `nodeId:versionId` pairs (max 100). Requires `chat_with_files` type and Intelligence enabled. | | `folders_scope` | string | No | All indexed files | Comma-separated `nodeId:depth` pairs (max 100, depth 1-10). Requires `chat_with_files` type and Intelligence enabled. | | `files_attach` | string | No | -- | Comma-separated **file** `nodeId:versionId` pairs (max 20, 200 MB total). Only file nodes accepted -- folder nodes are rejected. Cannot be combined with `files_scope`/`folders_scope`. Files must have AI summaries ready. | **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/" \ -H "Authorization: Bearer {jwt_token}" \ -d "type=chat_with_files" \ -d "question=What were the Q3 revenue figures?" \ -d "privacy=private" \ -d "personality=detailed" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "chat": { "id": "aBcDeFgHiJkLmNoPqR", "message": { "id": "xYzAbCdEfGhIjKlMnO" } } }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.chat.id` | string | Opaque ID of the created chat | | `response.chat.message.id` | string/null | Opaque ID of the initial message, or `null` if no question was provided | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Invalid `type`, `privacy`, or `personality` value | | `1605 (Invalid Input)` | 400 | File/folder scope used without Intelligence enabled | | `1605 (Invalid Input)` | 400 | Both `files_attach` and `files_scope` specified | | `1664 (Datastore Error)` | 500 | Chat or message creation failed | --- #### List chats (Share) ``` GET /current/share/{share_id}/ai/chat/list/ ``` Returns all chats created by the current user in the share. Sorted by most recently modified first. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Variant:** Append `/deleted` to the path to list deleted chats: `GET .../ai/chat/list/deleted` **Request example:** ```bash curl -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same format as workspace chat list. See [List chats](#list-chats) above. --- #### Get chat details (Share) ``` GET /current/share/{share_id}/ai/chat/{chat_id}/details/ ``` Returns chat details with full message history. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same format as workspace chat details. See [Get chat details](#get-chat-details) above. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1609 (Not Found)` | 404 | Chat ID not found | | `1654 (Internal Error)` | 500 | Internal failure loading chat | --- #### Update a chat (Share) ``` POST /current/share/{share_id}/ai/chat/{chat_id}/update/ ``` Update the name of an existing chat in a share. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | New chat name | **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "name=Updated Chat Name" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1605 (Invalid Input)` | 400 | Invalid name value | | `1664 (Datastore Error)` | 500 | Update failed | --- #### Delete a chat (Share) ``` DELETE /current/share/{share_id}/ai/chat/{chat_id}/ ``` **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -X DELETE "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1654 (Internal Error)` | 500 | Chat in non-deletable state or internal error | Deleted chats can be listed via `GET .../ai/chat/list/deleted`. --- #### Send a follow-up message (Share) ``` POST /current/share/{share_id}/ai/chat/{chat_id}/message/ ``` Send a new message to an existing chat in a share. The message is processed asynchronously. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `question` | string | Yes | -- | Follow-up question, 2-12,768 characters | | `personality` | string | No | `detailed` | `concise` or `detailed` | | `files_scope` | string | No | All indexed files | Comma-separated `nodeId:versionId` pairs (max 100). Only for `chat_with_files` type. | | `folders_scope` | string | No | All indexed files | Comma-separated `nodeId:depth` pairs (max 100, depth 1-10). Only for `chat_with_files` type. | | `files_attach` | string | No | -- | Comma-separated **file** `nodeId:versionId` pairs (max 20, 200 MB total). Only file nodes accepted -- folder nodes are rejected. Cannot be combined with scope params. | The chat type is inherited from the chat itself -- you do not need to specify `type` again. **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/" \ -H "Authorization: Bearer {jwt_token}" \ -d "question=How does that compare to Q2?" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "message": { "id": "mNoPqRsTuVwXyZaBcD" } }, "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1605 (Invalid Input)` | 400 | Both `files_attach` and `files_scope` specified | | `1605 (Invalid Input)` | 400 | Scope used with wrong chat type | | `1654 (Internal Error)` | 500 | Message creation or queuing failed | --- #### List messages in a chat (Share) ``` GET /current/share/{share_id}/ai/chat/{chat_id}/messages/list/ ``` Returns all messages in chronological order (oldest first). **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/messages/list/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same format as workspace message list. See [List messages in a chat](#list-messages-in-a-chat) above. --- #### Get message details (Share) ``` GET /current/share/{share_id}/ai/chat/{chat_id}/message/{message_id}/details/ ``` Retrieve detailed information about a specific message, including response text, citations, and cost. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Same format as workspace message details. See [Get message details](#get-message-details) above. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Message not found in the chat | | `1654 (Internal Error)` | 500 | Internal retrieval failure | --- #### Stream message response (SSE) (Share) ``` GET /current/share/{share_id}/ai/chat/{chat_id}/message/{message_id}/read/ ``` Returns a Server-Sent Events (SSE) stream of the AI response. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -N -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/read/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Accept: text/event-stream" ``` **SSE stream format and behavior:** Identical to the workspace version. See [Stream message response (SSE)](#stream-message-response-sse) above. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Message not found | | `1605 (Invalid Input)` | 400 | Message not in `in_progress` or `complete` state | | `1683 (Resource Missing)` | 404 | No SSE chunk data exists for this message | | `1654 (Internal Error)` | 500 | Internal streaming failure | --- #### Publish a private chat (Share) ``` POST /current/share/{share_id}/ai/chat/{chat_id}/publish/ ``` Makes a private chat public (visible to other share members). One-way operation -- published chats cannot be made private again. **Auth:** Bearer token required. Share `view` permission and chat permission. `content_ai` plan feature required. **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/publish/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "current_api_version": "1.0" } ``` **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1658 (Not Acceptable)` | 406 | Chat not found or locked | | `1660 (Conflict)` | 409 | Chat is already public | | `1664 (Datastore Error)` | 500 | Update failed | --- #### Generate AI Share (Share) ``` POST /current/share/{share_id}/ai/share/ ``` Generates markdown with temporary download URLs for selected files. Designed to be pasted into external AI chatbots (ChatGPT, Claude, etc.) to provide them with file context. **Auth:** Bearer token required. Share `view` permission and download permission. **Does NOT require** `content_ai` plan feature -- available on all plans. **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `files` | array (JSON) | Yes | JSON array of file opaque IDs. Min 1, max 25. | **Request example:** ```bash curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/share/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"files": ["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]}' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..." }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.markdown` | string | Generated markdown with file info and temporary download URLs | **Notes:** - Download URLs expire after 5 minutes (300 seconds) - Each token can be used a maximum of 3 times - Individual files limited to 50 MB; total size limited to 100 MB - When more than 5 files: titles only. 5 or fewer: includes full descriptions. - The `files` input is a JSON array (not comma-separated strings) **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Empty files array | | `1605 (Invalid Input)` | 400 | More than 25 files | | `1680 (Access Denied)` | 403 | Insufficient download permissions | --- ### Workspace AI vs. Share AI differences | Feature | Workspace AI | Share AI | |---|---|---| | AI Transactions endpoint | Yes | No | | Auto OG image endpoint | No | Yes | | Auto title endpoint | No | Yes | | `content_ai` feature required | Yes (except AI Share) | Yes (except AI Share) | | File scope context | Workspace files | Share files | --- ## AI Share File Download ``` GET /current/ai/share/{token}?file={index} ``` Download a file from an AI Share using a temporary token. **No authentication required** -- access is controlled by the token. | Parameter | Type | Required | Description | |---|---|---|---| | `{token}` | string (path) | Yes | Alphanumeric AI share token (generated by the AI Share creation endpoint) | | `file` | integer (query) | Yes | Zero-based file index within the AI Share | **Request example:** ```bash curl -X GET "https://api.fast.io/current/ai/share/aBcDeFgHiJkLmNoPqRsTuVwXyZ?file=0" \ -o downloaded_file.pdf ``` **Success response:** Binary file data with appropriate `Content-Type`, `Content-Disposition`, `Content-Length`, and `Accept-Ranges` headers. Supports HTTP range requests. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1609 (Not Found)` | 404 | Token missing, invalid, expired, or use limit reached | | `1609 (Not Found)` | 404 | File index out of range | | `1654 (Internal Error)` | 500 | Unable to retrieve or read file | All invalid/expired token errors return 404 to prevent token enumeration. --- ## Semantic Search > **Deprecated:** The `/ai/search/` endpoint is deprecated. Use `GET /current/workspace/{id}/storage/search/` (or `/share/{id}/storage/search/`) instead. The unified storage search endpoint automatically performs semantic search when workspace intelligence is enabled. Pass the `search` parameter for your query, and optionally `files_scope` and `folders_scope` to narrow results. Semantic results include `relevance_score`, `content_snippet`, `match_source`, `media_segment`, `mimetype`, and `search_metadata` fields. The legacy endpoints below continue to work but will be removed in a future release. ### Workspace Semantic Search (Deprecated) ``` GET /current/workspace/{workspace_id}/ai/search/ ``` **Auth:** Bearer token required. Workspace `view` permission. `content_ai` plan feature required. ### Share Semantic Search (Deprecated) ``` GET /current/share/{share_id}/ai/search/ ``` Identical to workspace semantic search but scoped to a share. **Parameters:** | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `query_text` | string | Yes | -- | Search query, 2-1,000 characters | | `files_scope` | string | No | All indexed files | Comma-separated `nodeId:versionId` pairs (max 100) | | `folders_scope` | string | No | All indexed files | Comma-separated `nodeId:depth` pairs (max 100, depth 1-10) | | `limit` | integer | No | 100 | Results per page, 1-500 | | `offset` | integer | No | 0 | Pagination offset | | `details` | string | No | false | When `"true"`, each result includes a `node` field with the full node resource (previews, AI state, versions, metadata, size). Default limit drops to 10 when enabled (enrichment is expensive). An explicit `limit` overrides this default. | **Preferred approach — use `/storage/search` instead:** ```bash # Basic search curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/search/?search=quarterly%20revenue&limit=10" \ -H "Authorization: Bearer {jwt_token}" # Search with full node details curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/search/?search=quarterly%20revenue&details=true" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": true, "response_code": 200, "results": [ { "content": "The quarterly revenue showed a 15% increase...", "score": 0.95, "node": { "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4", "type": "file", "name": "quarterly-report.pdf", "parent": "root", "size": 1048576, "mimetype": "application/pdf", "ai": { "state": "ready", "attach": true, "summary": true } } } ], "pagination": { "total": 25, "limit": 10, "offset": 0, "has_more": true } } ``` | Field | Type | Description | |---|---|---| | `results[].content` | string | Matched text snippet from the indexed document | | `results[].score` | float | Relevance score (0.0-1.0, higher is more relevant) | | `results[].node` | object/null | Full node resource, or `null` if the file was deleted | | `results[].file_details` | object | Raw metadata when `node` is null (node_id, version_id, name, mimetype) | | `pagination.total` | integer | Total number of results | | `pagination.has_more` | boolean | Whether more results are available | **Hybrid response fields (intelligence enabled):** When workspace intelligence is enabled, each file entry in the `/storage/search` response includes additional semantic fields: | Field | Type | Description | |---|---|---| | `relevance_score` | float | Semantic relevance score (0.0-1.0) | | `content_snippet` | string/null | The actual matching text from semantic search. NULL for keyword-only matches. | | `match_source` | string | Source of the match: `keyword`, `semantic`, or `both` | | `mimetype` | string | File MIME type (e.g., `application/pdf`, `audio/mpeg`). Present for semantic matches. | | `media_segment` | object | Only for audio/video matches when intelligence is on. Contains `start_seconds` and `end_seconds` for deep-linking to the exact timestamp range. | | `search_metadata` | object | Additional search metadata | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Intelligence not enabled | | `1605 (Invalid Input)` | 400 | Malformed `files_scope` or `folders_scope` | --- ## How to Phrase Questions ### With folder/file scope (RAG) Write questions that will match content in indexed files. The AI searches for relevant passages and cites them. Be specific. - Good: "What were the revenue figures for Q3 2025 compared to Q2?" - Good: "Summarize the key findings from the compliance audit reports" - Bad: "Tell me about these files" -- too vague, no searchable content to match ### With file attachments You can be more direct since the AI has the full file content. - Good: "Describe this image in detail" (with an image attached) - Good: "Extract all action items from this meeting transcript" - Good: "Compare these two contracts and list the differences" --- ## Chat Session Object Schema The chat resource is returned by details and list endpoints. | Field | Type | Description | |---|---|---| | `chat_id` | string | Opaque ID of the chat | | `creator` | object | `{type: string, id: string}` -- the chat creator | | `type` | string | `chat` or `chat_with_files` | | `name` | string | Display name of the chat | | `status` | string | Current chat status | | `message_count` | integer | Total number of messages | | `messages` | array | Full message list (details endpoint) or omitted | | `latest_message` | object | Most recent message (list endpoint) or omitted | | `unique_creators` | array | Distinct participants as `{type, id}` objects | | `cost` | object | `{credits: {tokens: int}}` -- total token cost | | `efficiency` | string | Efficiency rating | | `privacy` | object | `{visibility: "private"|"public", owner: {type, id}|null}` | | `created_at` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated_at` | string | Last update timestamp (`YYYY-MM-DD HH:MM:SS`) | --- ## Message Object Schema | Field | Type | Description | |---|---|---| | `chat_id` | string | Parent chat opaque ID | | `message_id` | string | Opaque ID of the message | | `creator` | object | `{type: string, id: string}` -- message author | | `personality` | string | AI personality mode used (`concise` or `detailed`) | | `state` | string | Processing state: `ready`, `in_progress`, `complete`, `errored` | | `message` | object | User's query (see sub-fields below) | | `message.text` | string | The question text | | `message.author_name` | string | User's given name | | `message.scope` | object | File/folder scope used for RAG context | | `message.attached` | array | Attached files as `{node_id, version_id}` objects | | `response` | object/null | AI response (`null` if not yet available) | | `response.text` | string | AI response text | | `response.error` | boolean | Whether an error occurred during processing | | `response.created` | string | Response timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.cost` | object | `{tokens: int, details: object}` -- message token cost | | `response.author_name` | string | AI persona name (e.g., `Ripley`) | | `response.events` | array | Status events generated during processing | | `response.table_data` | array | Structured table data in the response | | `response.analysis_chunks` | array | Analysis progress chunks | | `response.citations` | array | File citations (see below) | ### Citation Format Citations reference specific locations in files that informed the AI response. | Field | Type | Description | |---|---|---| | `hash` | string | File content hash (used for grouping) | | `nodeId` | string | Storage node opaque ID | | `versionId` | string | File version opaque ID | | `entries` | array | Citation locations within the file | | `entries[].page` | integer | Page number in the document | | `entries[].snippet` | string/null | Relevant text excerpt | | `entries[].timestamp` | float/null | Timestamp for audio/video files (seconds) | --- ## Activity Polling for AI Chat Completion **Do NOT poll the message details endpoint in a loop.** Use activity long-polling instead. ``` GET /current/activity/poll/{workspace_id}?wait=95&lastactivity={timestamp} ``` The server holds the connection for up to 95 seconds and returns immediately when something changes. Watch for the `ai_chat:{chatId}` activity key -- this fires when the message state changes. | Activity Key Pattern | What Changed | |---|---| | `ai_chat:{chatId}` | AI chat message state updated | | `storage:{fileId}` | File added, updated, or removed | | `preview:{fileId}` | File preview/thumbnail is ready | Pass the returned `lastactivity` timestamp into your next poll to receive only newer changes. **Anti-pattern:** Do not `GET .../ai/chat/{id}/message/{id}/details/` in a loop. Poll once on the workspace activity endpoint and wait for the `ai_chat` key. --- ## Metadata Templates Structured metadata for workspace files. Templates define schemas (fields, types, constraints) and have many-to-many relationships with files -- a template can be applied to multiple files, and files can have metadata from multiple templates. Templates are managed at the workspace level with plan-based limits (Free: 1, Pro: 1, Business: 10). Files are linked to templates either manually (add/remove endpoints) or automatically via AI-based matching. When intelligence is enabled, metadata is **automatically extracted during file ingestion** for documents, spreadsheets, images (PNG, JPEG, WebP), and code files. --- ### Create a template ``` POST /current/workspace/{workspace_id}/metadata/templates/ ``` Create a new metadata template defining fields, types, and constraints. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. **Parameters:** | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | Template name (1-255 characters) | | `description` | string | Yes | Template description | | `category` | string | Yes | Category (1-50 chars). Must be a valid category from the categories endpoint. | | `fields` | string (JSON) | Yes | JSON-encoded array of field definitions | **Field definition structure:** | Property | Type | Description | |---|---|---| | `name` | string | Field identifier (alphanumeric + underscore) | | `description` | string | Human-readable description | | `type` | string | `string`, `int`, `float`, `bool`, `json`, `url`, or `datetime` | | `min` | number | Minimum value/length constraint | | `max` | number | Maximum value/length constraint | | `default` | mixed | Default value | | `fixed_list` | array | Allowed values for dropdown-style fields | | `can_be_null` | boolean | Whether the field allows null values | **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/metadata/templates/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'name=Invoice+Template' \ -d 'description=Metadata+schema+for+invoices' \ -d 'category=financial' \ -d 'fields=[{"name":"invoice_number","description":"Invoice number","type":"string","min":1,"max":50,"can_be_null":false},{"name":"amount","description":"Total amount","type":"float","min":0,"can_be_null":false}]' ``` **Response (200 OK):** ```json { "result": "yes", "response": { "template": { "id": "mt_aBcDeFgHiJkLmN", "instanceId": "12345678901234567890", "orgId": "98765432109876543210", "category": "financial", "name": "Invoice Template", "description": "Metadata schema for invoices", "locked": false, "priority": null, "enabled": false, "deleted": null, "updated": "2025-01-20 10:30:00", "created": "2025-01-20 10:30:00", "fields": [ { "name": "invoice_number", "description": "Invoice number", "type": "string", "min": 1, "max": 50, "fixed_list": [], "can_be_null": false } ] } }, "current_api_version": "1.0" } ``` Newly created templates start with `enabled: false`. Use the settings endpoint to enable them. --- ### Delete a template ``` DELETE /current/workspace/{workspace_id}/metadata/templates/{template_id}/ ``` Soft-delete a metadata template. System templates cannot be deleted. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Template not found | | `1680 (Access Denied)` | 403 | Cannot delete a system template | | `1664 (Datastore Error)` | 500 | Deletion failed | --- ### List templates ``` GET /current/workspace/{workspace_id}/metadata/templates/list/ ``` **Auth:** Bearer token required. Workspace member. Metadata billing feature required. Optional path filter (append to URL): `all` (default, both system and custom), `custom` (non-system only), `system` (system only), `enabled`, or `disabled`. **Request example:** ```bash # List all templates curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/metadata/templates/list/" \ -H "Authorization: Bearer {jwt_token}" # List only enabled templates curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/metadata/templates/list/enabled/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "count": 2, "items": [ { "id": "mt_aBcDeFgHiJkLmN", "instanceId": "12345678901234567890", "orgId": "98765432109876543210", "category": "legal", "name": "Contract Template", "description": "Standard contract metadata fields", "locked": true, "priority": 3, "enabled": true, "deleted": null, "updated": "2025-01-15 08:00:00", "created": "2025-01-01 00:00:00", "fields": [ { "name": "contract_type", "description": "Type of contract", "type": "string", "fixed_list": ["NDA", "MSA", "SOW"], "can_be_null": false } ] } ] }, "current_api_version": "1.0" } ``` --- ### Get template details ``` GET /current/workspace/{workspace_id}/metadata/templates/{template_id}/details/ ``` **Auth:** Bearer token required. Workspace member. Metadata billing feature required. Returns the full template with fields and workspace-level instance settings. --- ### Update template settings ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/settings/ ``` Update per-workspace instance settings (enable/disable, priority). **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `enabled` | string | Yes | `"true"` or `"false"` | | `priority` | string | Yes | Priority level `"1"` through `"5"` | **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1683 (Resource Missing)` | 404 | Template not found | | `1609 (Not Found)` | 404 | Template is deleted | | `1680 (Access Denied)` | 403 | Template is locked | | `1664 (Datastore Error)` | 500 | Update failed | --- ### Update template definition ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/update/ ``` Update a template's field definitions. All body fields are optional -- only provided fields are updated. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | No | Updated template name (1-255 characters) | | `description` | string | No | Updated description (up to 1000 characters) | | `category` | string | No | Updated category (1-50 characters) | | `fields` | string (JSON) | No | Updated JSON-encoded array of field definitions | Append `/create/` to the path to copy the template instead of updating in place. --- ### Eligible files ``` GET /current/workspace/{workspace_id}/metadata/eligible/ ``` Paginated list of files eligible for metadata extraction (files that have both an AI summary and a preview ready). **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | No | Number of items to return (1-500, default: 100) | | `offset` | integer | No | Number of items to skip (default: 0) | **Response:** Standard paginated response with eligible file nodes. --- ### Add files to template ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/add/ ``` Manually add files to a template. Creates the many-to-many mapping between the template and the specified files. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `node_ids` | string (JSON) | Yes | JSON-encoded array of node IDs to add to the template | --- ### Remove files from template ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/remove/ ``` Remove files from a template. Removes the many-to-many mapping between the template and the specified files. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `node_ids` | string (JSON) | Yes | JSON-encoded array of node IDs to remove from the template | --- ### List files in template ``` GET /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/ ``` List all files currently mapped to a template. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | No | Number of items to return (1-500, default: 100) | | `offset` | integer | No | Number of items to skip (default: 0) | **Response:** Standard paginated response with mapped file nodes. --- ### Preview template match ``` POST /current/workspace/{workspace_id}/metadata/templates/preview-match/ ``` Synchronously preview which files would match a proposed metadata template before creating it. Accepts a template name and description, scans up to 20 eligible files using AI classification, and returns the matched files with details. No template is created -- this is a read-only preview operation. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | Proposed template name (1-255 characters) | | `description` | string | Yes | Proposed template description (1-2000 characters) | **Response (200 OK):** ```json { "result": "yes", "response": { "matched_files": [ { "node_id": "aBcDeFgHiJkLmN", "name": "invoice_2025.pdf", "mimetype": "application/pdf", "summary_title": "Invoice from Acme Corp", "summary_short": "Invoice INV-2025-001 for $1,500.00" } ], "total_eligible": 45, "total_scanned": 20, "total_matched": 1 } } ``` | Field | Type | Description | |---|---|---| | `matched_files` | array | Files that matched the proposed template | | `matched_files[].node_id` | string | File node opaque ID | | `matched_files[].name` | string | File name | | `matched_files[].mimetype` | string | MIME type | | `matched_files[].summary_title` | string | AI-generated summary title | | `matched_files[].summary_short` | string | AI-generated short summary | | `total_eligible` | integer | Total eligible files in the workspace | | `total_scanned` | integer | Files scanned (up to 20) | | `total_matched` | integer | Files that matched | Requires available AI credits. Only files with completed AI summaries and previews are eligible. --- ### Auto-match files to template ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/auto-match/ ``` Use AI to automatically match eligible files in the workspace to the template based on file content and template field definitions. Progress is tracked via the jobs status endpoint under the `template_match` key. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. **Response:** Returns the list of matched file nodes that were added to the template. --- ### Batch extract metadata (template-level) ``` POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/extract-all/ ``` Batch-extract metadata for all files mapped to a template. Async -- returns a `job_id` for tracking. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. **Response (200 OK):** ```json { "result": "yes", "response": { "job_id": "aj_aBcDeFgHiJkLmN", "template_id": "mt_oPqRsTuVwXyZ12" } } ``` Maximum 1,000 files processed per job. --- ## Node Metadata Metadata stored on individual files. Split into **template metadata** (conforming to mapped templates) and **custom metadata** (user-defined fields). --- ### Get file metadata ``` GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/details/ ``` Returns all metadata for a file. Response contains `template_metadata` and `custom_metadata` separately. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. **Request example:** ```bash curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/storage/aBcDeFgHiJkLmN/metadata/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "instance_id": "1234567890123456789", "object_id": "aBcDeFgHiJkLmN", "template_id": "mt_oPqRsTuVwXyZ12", "node_id": { "id": "aBcDeFgHiJkLmN", "name": "invoice_2025.pdf", "type": "file", "size": 245760 }, "metadata": [ { "key": "invoice_number", "description": "Invoice number", "type": "string", "value": "INV-2025-001", "is_auto": false, "updated": "2025-01-20 10:30:00" }, { "key": "amount", "description": "Invoice amount", "type": "float", "value": 1500.00, "is_auto": true, "updated": "2025-01-20 10:30:00" } ] }, "current_api_version": "1.0" } ``` | Field | Type | Description | |---|---|---| | `response.template_id` | string/null | Associated template ID, or null | | `response.node_id` | object | Storage node resource with file details | | `response.metadata[].key` | string | Metadata field key | | `response.metadata[].type` | string | Value type (`string`, `int`, `float`, `bool`, `json`, `url`, `datetime`) | | `response.metadata[].value` | mixed | The metadata value | | `response.metadata[].is_auto` | boolean | Whether the value was auto-generated by AI extraction | | `response.metadata[].updated` | string | Last update timestamp | --- ### Update file metadata ``` POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/update/{template_id}/ ``` Set or update metadata key-value pairs on a file. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `key_values` | string (JSON) | Yes | JSON-encoded object of key-value pairs. Keys must match template field names. | **Request example:** ```bash curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/storage/aBcDeFgHiJkLmN/metadata/update/mt_oPqRsTuVwXyZ12/" \ -H "Authorization: Bearer {jwt_token}" \ -d 'key_values={"invoice_number":"INV-2025-001","amount":1500.00}' ``` Existing metadata for the same keys is overwritten. Keys not included in the request are left unchanged. --- ### Delete file metadata ``` DELETE /current/workspace/{workspace_id}/storage/{node_id}/metadata/ ``` Delete metadata keys from a file. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `keys` | string (JSON) | No | JSON-encoded array of key names to delete. If omitted, all keys may be removed. | Only files and notes support metadata -- folders return an error. --- ### Extract metadata (single file) ``` POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/extract/ ``` AI-extracts metadata from a file using a specified template. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `template_id` | string | Yes | The template ID to extract metadata against | Supports documents, spreadsheets, images (PNG, JPEG, WebP), and code files. Extraction is synchronous. Extracted values are stored with `is_auto: true`. **Error responses:** | Error Code | HTTP Status | Cause | |---|---|---| | `1605 (Invalid Input)` | 400 | Node is root (use template-level `extract-all` for batch extraction) | | `1609 (Not Found)` | 404 | Template not found | | `1609 (Not Found)` | 404 | Node not found | | `1664 (Datastore Error)` | 500 | Extraction failed | --- ### List metadata by template ``` GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/list/{template_id}/ ``` List all files with metadata for a specific template. Supports filtering and sorting. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. | Parameter | Type | Required | Description | |---|---|---|---| | `filters` | string (JSON) | No | JSON-encoded filter criteria | | `order_by` | string | No | Field key name to sort by | | `order_desc` | string | No | `"true"` or `"false"` for descending sort | --- ### List templates in use ``` GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/templates/ ``` List templates that have metadata set on files, with object counts. **Auth:** Bearer token required. Workspace member. Metadata billing feature required. --- ### Metadata versions ``` GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/versions/ ``` List metadata version snapshots for a file. Useful for tracking changes to extracted metadata over time. --- ### Metadata views Save, list, and delete custom views for metadata display. **Create/update a view:** ``` POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/view/ ``` | Parameter | Type | Required | Description | |---|---|---|---| | `name` | string | Yes | View name (1-30 characters) | | `template_id` | string | Yes | Template ID to associate | | `filters` | string (JSON) | No | Filter criteria | | `order_by` | string | No | Sort field | | `order_desc` | string | No | `"true"` or `"false"` for descending | **List views:** ``` GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/views/ ``` **Delete a view:** ``` DELETE /current/workspace/{workspace_id}/storage/{node_id}/metadata/view/{view_id}/ ``` --- ## Jobs Status (Unified Async Processing) A single endpoint to check the status of all async processing jobs (AI indexing, metadata extraction) for a workspace or share. Replaces the removed `metadata/intelligence/status` and `metadata/templates/{id}/extract-status` endpoints. ### Workspace jobs status ``` GET /current/workspace/{workspace_id}/jobs/status/ ``` **Auth:** Workspace member. **Feature gate:** AI feature must be enabled on the organization plan. ### Share jobs status ``` GET /current/share/{share_id}/jobs/status/ ``` **Auth:** Share viewer. **Feature gate:** AI feature must be enabled on the organization plan. ### Response ```json { "result": "yes", "response": { "jobs": { "intelligence": { "active": true, "status": "ingesting", "direction": "enable", "total_files": 100, "eligible_files": 80, "processed": 30, "skipped": 5, "failed": 0, "progress_percent": 37, "started_at": 1711500000, "updated_at": 1711500300, "completed_at": null, "stop_reason": null }, "metadata_extract": [ { "active": true, "template_id": "12345678901234567890", "status": "extracting", "total_files": 50, "eligible_files": 40, "processed": 24, "skipped": 2, "failed": 0, "progress_percent": 60, "started_at": 1711500000, "updated_at": 1711500200, "completed_at": null, "stop_reason": null, "fields_scope": ["name", "date", "amount"] } ] } } } ``` **Response fields:** | Field | Type | Description | |---|---|---| | `jobs.intelligence` | object/null | AI indexing job status, or `null` if no job exists | | `jobs.intelligence.active` | boolean | Whether the job is currently processing | | `jobs.intelligence.status` | string | `starting`, `ingesting`, `flushing`, `draining`, `completed`, `failed`, or `stopped` | | `jobs.intelligence.direction` | string | `enable` (indexing files) or `disable` (removing embeddings) | | `jobs.intelligence.total_files` | integer | Total files in the workspace/share | | `jobs.intelligence.eligible_files` | integer | Files eligible for processing | | `jobs.intelligence.processed` | integer | Files processed so far | | `jobs.intelligence.skipped` | integer | Files skipped | | `jobs.intelligence.failed` | integer | Files that failed processing | | `jobs.intelligence.progress_percent` | integer | 0-100 progress based on processed/eligible | | `jobs.intelligence.started_at` | integer | Unix timestamp when job started | | `jobs.intelligence.updated_at` | integer | Unix timestamp of last progress update | | `jobs.intelligence.completed_at` | integer/null | Unix timestamp when completed, or `null` | | `jobs.intelligence.stop_reason` | string/null | Reason the job stopped early, or `null` | | `jobs.metadata_extract` | array | Per-template extraction statuses (empty array if none) | | `jobs.metadata_extract[].active` | boolean | Whether extraction is currently running | | `jobs.metadata_extract[].template_id` | string | The template identifier | | `jobs.metadata_extract[].status` | string | `queued`, `starting`, `walking`, `extracting`, or completed states | | `jobs.metadata_extract[].fields_scope` | array/null | Template field names being extracted | Other fields in each `metadata_extract` entry match the intelligence fields (`total_files`, `eligible_files`, `processed`, `skipped`, `failed`, `progress_percent`, `started_at`, `updated_at`, `completed_at`, `stop_reason`). **Real-time updates:** Both job types broadcast via the Activity/WebSocket system. Clients subscribed to the workspace or share WebSocket channel receive activity notifications when progress changes, reducing the need for polling. --- ## Template Resource Schema | Field | Type | Description | |---|---|---| | `id` | string | Opaque template identifier | | `instanceId` | string | Workspace ID | | `orgId` | string/null | Organization ID | | `category` | string | Template category | | `name` | string | Template name | | `description` | string | Template description | | `locked` | boolean | Whether the template is locked from editing | | `priority` | integer/null | Priority level 1-5, or null | | `enabled` | boolean | Whether enabled for the workspace | | `deleted` | string/null | Soft-delete timestamp, or null | | `updated` | string | Last updated timestamp | | `created` | string | Creation timestamp | | `fields` | array | Array of field definition objects | ### Supported Field Types | Type | Description | |---|---| | `string` | Text values (max 10,000 characters) | | `int` | Integer numbers | | `float` | Decimal numbers | | `bool` | Boolean true/false | | `json` | Complex JSON structures | | `url` | Validated URLs | | `datetime` | Date and time values | --- ## Quick Reference ### Create a RAG chat and get the answer: ``` POST /current/workspace/{id}/ai/chat/ question=...&type=chat_with_files&personality=detailed -> chat_id, message_id GET /current/activity/poll/{id}?wait=95&lastactivity=... -> wait for ai_chat:{chatId} GET /current/workspace/{id}/ai/chat/{chat_id}/message/{msg_id}/details/ -> check state == complete GET /current/workspace/{id}/ai/chat/{chat_id}/message/{msg_id}/read/ -> SSE stream: data, analysis_data, table_data, done ``` ### Create a note (bank knowledge for RAG): ``` POST /current/workspace/{id}/storage/{parent}/createnote/ name=research-notes.md&content=... ``` ### Extract metadata from a file: ``` POST /current/workspace/{id}/storage/{node}/metadata/extract/ template_id={template_id} ``` ### List eligible files for metadata: ``` GET /current/workspace/{id}/metadata/eligible/ ``` ### Add files to a template: ``` POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/add/ node_ids=["nodeId1","nodeId2"] ``` ### AI auto-match files to a template: ``` POST /current/workspace/{id}/metadata/templates/{template_id}/auto-match/ ``` ### Batch extract metadata for all files in a template: ``` POST /current/workspace/{id}/metadata/templates/{template_id}/extract-all/ -> returns job_id (rate limited) ``` ### Check status of all async jobs (intelligence + metadata extraction): ``` GET /current/workspace/{id}/jobs/status/ GET /current/share/{id}/jobs/status/ -> returns jobs.intelligence and jobs.metadata_extract[] ``` ### Semantic search (use `/storage/search` instead — `/ai/search` is deprecated): ``` GET /current/workspace/{id}/storage/search/?search=quarterly+revenue&limit=10 GET /current/share/{id}/storage/search/?search=quarterly+revenue&limit=10 GET /current/workspace/{id}/storage/search/?search=quarterly+revenue&details=true ``` Optional `details=true` includes full node resource (previews, AI state, metadata, size) per result. Default limit drops to 10 when details enabled. ### Share AI chat (same workflow as workspace): ``` POST /current/share/{id}/ai/chat/ question=...&type=chat_with_files&personality=detailed -> chat_id, message_id GET /current/share/{id}/ai/chat/{chat_id}/message/{msg_id}/details/ -> check state == complete GET /current/share/{id}/ai/chat/{chat_id}/message/{msg_id}/read/ -> SSE stream: data, analysis_data, table_data, done ``` ### Share-specific AI: ``` GET /current/share/{id}/ai/autoog/ -> binary PNG image (OG image) POST /current/share/{id}/ai/autotitle/ -> title, description, display_type POST /current/share/{id}/ai/share/ files=["opaqueId1","opaqueId2"] -> markdown with download URLs ``` > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Events, Activity & Realtime Base URL: `https://api.fast.io/current/` Auth: All endpoints require `Authorization: Bearer {jwt_token}` unless noted. Response format: JSON (standard envelope with `result`, `status`, and data fields). --- ## Events Search Search and filter the event log. Events capture every action in the system -- file operations, membership changes, comments, AI activity, billing, workflow, and more. --- ### `GET /current/events/search/` Search and filter events with comprehensive filtering options. Uses offset-based pagination. **Auth:** Required (JWT). Rate limited: 5 per 3s, 15 per 30s, 120 per 10min (shared rate limit). **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `user_id` | string | Conditional | - | 19-digit numeric ID | Filter by user profile ID. One of `user_id`, `org_id`, `workspace_id`, `share_id`, or `parent_event_id` is required. | | `org_id` | string | Conditional | - | 19-digit numeric ID | Filter by organization profile ID | | `workspace_id` | string | Conditional | - | 19-digit numeric ID | Filter by workspace profile ID | | `share_id` | string | Conditional | - | 19-digit numeric ID | Filter by share profile ID | | `parent_event_id` | string | Conditional | - | Alphanumeric OpaqueId | Filter by parent event ID for serial/batch events. Cannot combine with filters other than `acknowledged`, `limit`, `offset`. | | `event` | string | No | - | Max 100 characters | Filter by specific event name (e.g., `workspace_storage_file_added`) | | `category` | string | No | - | See Event Categories | Filter by event category | | `subcategory` | string | No | - | See Event Subcategories | Filter by event subcategory | | `calling_user_id` | string | No | - | 19-digit numeric ID | Filter by the user who triggered the event | | `object_id` | string | No | - | Alphanumeric OpaqueId | Filter by related object ID (file, folder, etc.) | | `acknowledged` | string | No | - | `"true"` or `"false"` | Filter by acknowledgment status | | `visibility` | string | No | All non-internal | `"external_audit_log"` or `"external"` | Filter by event visibility level | | `created-min` | string | No | - | Accepts ISO 8601 (e.g., `2025-12-01T06:00:00Z`) or `YYYY-MM-DD HH:MM:SS` format | Lower bound for event creation time | | `created-max` | string | No | - | Same format as `created-min`; must be > `created-min` | Upper bound for event creation time | | `limit` | integer | No | `100` | 1-250 | Maximum number of results | | `offset` | integer | No | `0` | 0+ | Number of results to skip for pagination | **Profile filter priority:** If multiple profile filters are supplied, priority is: `user_id` > `org_id` > `workspace_id` > `share_id`. Only the highest-priority filter is applied. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/events/search/?workspace_id=12345678901234567890&category=workspace&subcategory=storage&limit=50" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "events": [ { "event_id": "evt_abc123xyz789", "created": "2025-01-20 10:30:45", "acknowledged": false, "event": "workspace_storage_file_added", "category": "workspace", "subcategory": "storage", "object_id": "node_def456ghi789", "calling_user_id": "98765432109876543210", "calling_user_name": "Jane Smith", "org_id": "11111111111111111111", "workspace_id": "12345678901234567890", "filename": "quarterly_report.pdf", "file_size": 2485760 } ] } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.events` | array | Array of event objects | | `response.events[].event_id` | string | Unique event identifier (alphanumeric OpaqueId) | | `response.events[].created` | string | Event timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.events[].acknowledged` | boolean | Whether the current user has acknowledged this event | | `response.events[].event` | string | Event name identifier (e.g., `workspace_storage_file_added`) | | `response.events[].category` | string | Event category name | | `response.events[].subcategory` | string | Event subcategory name | | `response.events[].object_id` | string | Related object OpaqueId (if applicable) | | `response.events[].calling_user_id` | string | 19-digit numeric ID of the triggering user | | `response.events[].calling_user_name` | string | Display name of the triggering user | | `response.events[].org_id` | string | Organization ID context (if applicable) | | `response.events[].workspace_id` | string | Workspace ID context (if applicable) | | `response.events[].share_id` | string | Share ID context (if applicable) | | `response.events[].user_id` | string | Target user ID for user-specific events (if applicable) | Additional event-specific fields (e.g., `filename`, `file_size`, `member_name`) vary by event type. **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | No profile filter or `parent_event_id` provided ("Event profile was missing.") | | `1605 (Invalid Input)` | 400 | `created-min` is greater than `created-max` | | `1605 (Invalid Input)` | 400 | `parent_event_id` combined with disallowed filters | | `1605 (Invalid Input)` | 400 | Invalid datetime format for `created-min` or `created-max` | | `1680 (Access Denied)` | 403 | Token scope does not include the requested profile | | `1600 (Query Error)` | 500 | Internal error during event search | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT token | | `1651 (Invalid Request Type)` | 400 | Wrong HTTP method (only GET accepted) | **Notes:** - Results may be slightly delayed due to caching. - OAuth scoped tokens enforce entity-level access: events are filtered to only entities within the token's scope. - Events the user cannot access are automatically excluded. --- ### `GET /current/events/search/summarize/` Search events and generate an AI-powered natural language summary. Accepts all parameters from `/events/search/` plus `user_context`. **Auth:** Required (JWT). Rate limited: shares the search rate limit. **Additional Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `user_context` | string | No | `""` | Max 64 chars; letters, numbers, spaces, `. , ! ? ' -` only | Focus guidance for the AI summary (e.g., `"Focus on uploads"`) | All other parameters are identical to `GET /current/events/search/`. **curl Example:** ```bash curl -X GET "https://api.fast.io/current/events/search/summarize/?workspace_id=12345678901234567890&user_context=Focus%20on%20file%20uploads&limit=100" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "summary": { "text": "@[user:98765432109876543210:Jane Smith] uploaded 12 files to the project folder, including quarterly reports and design assets. @[user:55555555555555555555:John Doe] added 3 new members to the workspace.", "metrics": { "total_events": 50, "unique_actors": 5, "date_range": { "start": "2025-01-01T00:00:00+00:00", "end": "2025-01-20T10:30:45+00:00" }, "categories": { "storage": 30, "members": 20 } } }, "events": [ ... ] } } ``` **Response Fields (additional to events search):** | Field | Type | Description | |-------|------|-------------| | `response.summary` | object or null | AI-generated summary, or `null` if no events or generation failed | | `response.summary.text` | string | Natural language summary with `@[type:ID:name]` mention pills | | `response.summary.metrics.total_events` | integer | Total events summarized | | `response.summary.metrics.unique_actors` | integer | Distinct users who triggered events | | `response.summary.metrics.date_range.start` | string | Earliest event timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.summary.metrics.date_range.end` | string | Most recent event timestamp (`YYYY-MM-DD HH:MM:SS`) | | `response.summary.metrics.categories` | object | Map of category names to event counts | **Summary Mention Pill Formats:** | Entity | Format | Example | |--------|--------|---------| | User | `@[user:USER_ID:Display Name]` | `@[user:98765432109876543210:Jane Smith]` | | File | `@[file:FILE_ID:filename.ext]` | `@[file:node_abc123:report.pdf]` | | Folder | `@[folder:FOLDER_ID:foldername]` | `@[folder:node_def456:Projects]` | | Workspace | `@[workspace:WS_ID:name]` | `@[workspace:12345678901234567890:Engineering]` | | Share | `@[share:SHARE_ID:name]` | `@[share:55555555555555555555:Client Files]` | **Error Responses:** All errors from `/events/search/` apply. Summary generation failures are non-fatal: `summary` is `null` but events are still returned. **Notes:** - Requires a billable organization for AI token billing (resolved from profile context). - AI token consumption is billed to the associated organization. --- ### `GET /current/event/{event_id}/details/` Get full details for a single event. **Auth:** Required (JWT). Default rate limiting. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{event_id}` | string | Yes | Alphanumeric OpaqueId of the event | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/event/evt_abc123xyz789/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "event": { "event_id": "evt_abc123xyz789", "created": "2025-01-20 10:30:45", "acknowledged": false, "event": "workspace_storage_file_added", "category": "workspace", "subcategory": "storage", "object_id": "node_def456ghi789", "calling_user_id": "98765432109876543210", "calling_user_name": "Jane Smith", "org_id": "11111111111111111111", "workspace_id": "12345678901234567890", "filename": "quarterly_report.pdf", "file_size": 2485760 } } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.event` | object | Full event object (same fields as event search results) | **Access Rules:** | Condition | Access | |-----------|--------| | User is the `calling_user` | Granted | | User is the `event_user` (target) | Granted | | Event has `targeted` permission and user is neither target nor caller | Denied | | Event is `internal` visibility | Always denied | | User has appropriate profile-level permissions | Granted based on permission level | **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | Event ID missing, empty, or not a valid OpaqueId | | `1609 (Not Found)` | 404 | No event exists with the provided ID | | `1605 (Invalid Input)` | 400 | Event has `internal` visibility | | `1605 (Invalid Input)` | 400 | User lacks permission (targeted event) | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT token | | `1651 (Invalid Request Type)` | 400 | Wrong HTTP method (only GET accepted) | --- ### `POST /current/event/{event_id}/ack/` Acknowledge (mark as read) an event for the current user. Idempotent: acknowledging an already-acknowledged event succeeds silently. **Auth:** Required (JWT). Default rate limiting. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{event_id}` | string | Yes | Alphanumeric OpaqueId of the event to acknowledge | **curl Example:** ```bash curl -X POST "https://api.fast.io/current/event/evt_abc123xyz789/ack/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes" } ``` **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | Event ID missing or invalid | | `1609 (Not Found)` | 404 | Event not found | | `1605 (Invalid Input)` | 400 | Event has `internal` visibility | | `1605 (Invalid Input)` | 400 | User lacks permission (targeted event) | | `1664 (Datastore Error)` | 500 | Failed to persist the acknowledgment | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT token | **Notes:** - Acknowledgment is per-user. Acknowledging for one user does not affect others. - Same access rules as event details apply. --- ## Event Categories | Category | API Value | Description | |----------|-----------|-------------| | Upload | `upload` | File upload operations | | User | `user` | User account events | | Organization | `org` | Organization events | | Workspace | `workspace` | Workspace operations | | Share | `share` | Share operations | | AI | `ai` | AI/ML operations | | Invitation | `invitation` | Invitation events | | Email | `email` | Email-related events | | Billing | `billing` | Billing and subscription events | | Metadata | `metadata` | Metadata operations | | Domain | `domain` | Custom domain events | | Apps | `apps` | Application/integration events | | Workflow | `workflow` | Workflow events (task lists, tasks, worklogs, approvals, todos) | --- ## Event Subcategories | Subcategory | API Value | Description | |-------------|-----------|-------------| | Storage | `storage` | File and folder operations | | Comments | `comments` | Comment activity | | Members | `members` | Membership changes | | Lifecycle | `lifecycle` | Create, update, delete, archive events | | Settings | `settings` | Configuration changes | | Security | `security` | Security-related events | | Authentication | `authentication` | Login and auth events | | AI | `ai` | AI processing events | | Invitations | `invitations` | Invitation management | | Billing | `billing` | Subscription and payment | | Assets | `assets` | Asset (avatar, branding) updates | | Upload | `upload` | Upload events | | Transfer | `transfer` | Ownership transfer events | | Import/Export | `import_export` | Import/export operations | | Quick Share | `quickshare` | Quick share events | | Metadata | `metadata` | Metadata operations | | Workflow | `workflow` | Workflow events | --- ## Event Visibility Levels | Visibility | API Value | Description | |------------|-----------|-------------| | Internal | `internal` | System events. Never accessible via API. | | Audit Log | `external_audit_log` | Audit/compliance events. Targeted permission checks bypassed for admins. | | External | `external` | Standard user-facing events. | Default (no `visibility` parameter): returns both `external_audit_log` and `external` events, excludes `internal`. --- ## Event Permission Levels | Permission | Description | |------------|-------------| | `member` | Any member of the profile can view | | `admin` | Only admins of the profile can view | | `targeted` | Only the target user or the calling user can view | When querying with `visibility=external_audit_log`, targeted permission checks are bypassed. --- ## Event Names Reference ### Workspace Storage - `workspace_storage_file_added` -- File uploaded - `workspace_storage_file_deleted` -- File trashed - `workspace_storage_file_moved` -- File moved - `workspace_storage_file_copied` -- File copied - `workspace_storage_file_updated` -- File metadata updated - `workspace_storage_file_restored` -- File restored from trash - `workspace_storage_file_version_restored` -- File version restored - `workspace_storage_folder_created` -- Folder created - `workspace_storage_folder_deleted` -- Folder trashed - `workspace_storage_folder_moved` -- Folder moved - `workspace_storage_download_token_created` -- Download token issued - `workspace_storage_zip_downloaded` -- ZIP download completed - `workspace_storage_link_added` -- Link added ### Share Storage - `share_storage_file_added` -- File uploaded - `share_storage_file_deleted` -- File trashed - `share_storage_file_moved` -- File moved - `share_storage_file_copied` -- File copied - `share_storage_file_updated` -- File metadata updated - `share_storage_file_restored` -- File restored - `share_storage_folder_created` -- Folder created - `share_storage_folder_deleted` -- Folder trashed - `share_storage_folder_moved` -- Folder moved - `share_storage_download_token_created` -- Download token issued - `share_storage_zip_downloaded` -- ZIP download completed ### Comments - `comment_created` -- Comment created - `comment_updated` -- Comment updated - `comment_deleted` -- Comment deleted - `comment_mentioned` -- User mentioned in comment (targeted permission) - `comment_replied` -- Reply to a comment - `comment_reaction` -- Reaction added ### Membership - `added_member_to_org` / `removed_member_from_org` -- Org membership - `added_member_to_workspace` / `removed_member_from_workspace` -- Workspace membership - `added_member_to_share` / `removed_member_from_share` -- Share membership - `membership_updated` -- Permission changes ### Workspace Lifecycle - `workspace_created` / `workspace_updated` / `workspace_deleted` - `workspace_archived` / `workspace_unarchived` ### Share Lifecycle - `share_created` / `share_updated` / `share_deleted` - `share_archived` / `share_unarchived` - `share_imported_to_workspace` -- Share imported into workspace ### AI - `ai_chat_created` / `ai_chat_updated` / `ai_chat_deleted` -- Chat lifecycle - `ai_chat_new_message` -- New AI chat message - `ai_chat_published` -- Chat published - `node_ai_summary_created` -- AI summary generated for a file - `workspace_ai_share_created` -- AI share created in workspace ### Metadata - `metadata_kv_update` / `metadata_kv_delete` / `metadata_kv_extract` - `metadata_template_update` / `metadata_template_delete` / `metadata_template_select` - `metadata_view_update` / `metadata_view_delete` ### Quick Shares - `workspace_quickshare_created` / `workspace_quickshare_updated` / `workspace_quickshare_deleted` - `workspace_quickshare_file_downloaded` / `workspace_quickshare_file_previewed` ### Invitations - `invitation_email_sent` / `invitation_accepted` / `invitation_declined` ### User - `user_created` / `user_updated` / `user_deleted` - `user_email_reset` / `user_asset_updated` ### Organization - `org_created` / `org_updated` / `org_closed` - `org_transfer_token_created` / `org_transfer_completed` ### Billing - `subscription_created` / `subscription_cancelled` - `billing_free_trial_ended` ### Workflow - `task_list_created` / `task_list_updated` / `task_list_deleted` - `task_created` / `task_updated` / `task_deleted` - `task_status_changed` / `task_assigned` / `task_completed` - `worklog_entry_created` - `worklog_interjection_created` / `worklog_interjection_acknowledged` - `approval_requested` / `approval_approved` / `approval_rejected` - `todo_created` / `todo_toggled` / `todo_deleted` / `todo_updated` --- ## Event Search Examples **Recent comments in a workspace:** ``` GET /current/events/search/?workspace_id={id}&subcategory=comments ``` **File uploads to a share in a date range:** ``` GET /current/events/search/?share_id={id}&event=share_storage_file_added&created-min=2025-12-01T06:00:00Z ``` **Membership changes in an org:** ``` GET /current/events/search/?org_id={id}&subcategory=members ``` **AI activity in a workspace:** ``` GET /current/events/search/?workspace_id={id}&category=ai ``` **Unacknowledged events for a user:** ``` GET /current/events/search/?user_id={id}&acknowledged=false ``` **Audit log events only:** ``` GET /current/events/search/?workspace_id={id}&visibility=external_audit_log&limit=100 ``` **Child events of a batch operation:** ``` GET /current/events/search/?parent_event_id=evt_abc123xyz789&limit=100 ``` **Workflow activity in a workspace:** ``` GET /current/events/search/?workspace_id={id}&category=workflow ``` --- ## Activity Polling Long-poll endpoints for efficient change detection. The server holds the connection open and returns immediately when something changes, avoiding expensive resource polling. --- ### `GET /current/activity/poll/` Poll for activity updates on the current user's profile. **Auth:** Required (JWT). Rate limited: 200 per 60s, 1000 per hour. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `wait` | integer | No | `0` | 0-95 | Long-poll timeout in seconds. Server holds connection open until update or timeout. | | `lastactivity` | string | No | Current time | Micro-precision datetime (e.g., `2025-01-20 10:30:45.123456`) | Only return activity newer than this timestamp | | `updated` | any | No | - | Any value = enabled | If present, only return activity fields updated since `lastactivity` | | `fields` | string | No | All fields | Comma-delimited; max 30 fields | Activity field names to check. Supports ID qualifier via colon (e.g., `storage:12345`). | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/activity/poll/?wait=30&lastactivity=2025-01-20%2010:30:45.123456" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK -- activity found):** ```json { "result": "yes", "response": { "results": 3, "activity": { "storage": "2025-01-20 10:30:45.123456 UTC", "members": "2025-01-20 09:15:22.654321 UTC", "settings": "2025-01-19 14:00:00.000000 UTC" }, "lastactivity": "2025-01-20 10:30:45.123456 UTC" } } ``` **Response (200 OK -- no activity):** ```json { "result": "yes", "response": { "results": 0, "activity": [] } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.results` | integer | Number of activity fields returned | | `response.activity` | object | Map of activity field names to micro-precision UTC timestamps | | `response.lastactivity` | string | Most recent timestamp; pass as `lastactivity` in next poll | **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | Invalid profile ID format | | `1605 (Invalid Input)` | 400 | User lacks permissions for the specified profile | | `1605 (Invalid Input)` | 400 | Invalid field names or more than 30 fields | | `1665 (Object Init Failed)` | 500 | Internal error | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT token | --- ### `GET /current/activity/poll/{profile_id}/` Poll for activity updates on a specific workspace, share, or org. **Auth:** Required (JWT). Same rate limits and parameters as `GET /current/activity/poll/`. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{profile_id}` | string | Yes | 19-digit numeric profile ID (org, workspace, or share). For upload progress, use your user ID. | **Access Requirements:** | Profile Type | Permission Required | |--------------|-------------------| | User (self) | Authenticated | | Organization | `PERM_VIEW` on the org | | Workspace | `PERM_VIEW` on the workspace | | Share | Share view permissions + multiplayer enabled (multiplayer status is automatically determined based on share configuration -- not directly togglable via API) | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/activity/poll/12345678901234567890/?wait=30&fields=storage,members&updated=1&lastactivity=2025-01-20%2010:30:45.123456" \ -H "Authorization: Bearer {jwt_token}" ``` Response format is identical to `GET /current/activity/poll/`. --- ### Polling Workflow 1. Make initial poll request (no `lastactivity` parameter) 2. Receive response with `activity` fields and `lastactivity` timestamp 3. Process changes by fetching updated resources based on activity field names 4. Make next poll with `lastactivity` from previous response 5. Repeat -- server returns immediately on change, or after `wait` seconds timeout ### Activity Key Patterns | Key Pattern | What Changed | |-------------|-------------| | `storage:{fileId}` | File added, updated, or removed | | `preview:{fileId}` | File preview/thumbnail is ready | | `ai_chat:{chatId}` | AI chat message updated | | `comments:{nodeId}` | Comment added or updated | | `member:{userId}` | Membership changed | ### Anti-Patterns - **Do NOT** loop on resource detail endpoints to wait for previews or processing. - **Instead**, poll on the workspace/share and watch for the relevant activity key. - **For uploads**, use your user ID as the `profile_id` since upload progress is tied to the user, not a workspace. --- ## WebSocket (Real-Time) Optional real-time delivery (~300ms latency vs ~1s for polling). Sends both `activity` messages (field names for change detection) and enriched `event` messages (full event details) via WebSocket. Backwards compatible -- existing clients continue to work without changes. --- ### `GET /current/websocket/auth/{profile_id}` Generate a WebSocket authentication JWT for a specific profile. Tokens are valid for 24 hours. **Auth:** Required (JWT). No credit consumption. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{profile_id}` | string | Yes | 19-digit numeric ID of the user, org, workspace, or share to subscribe to | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/websocket/auth/12345678901234567890" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "expires_in": 86400, "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.expires_in` | integer | Token lifetime in seconds (86400 = 24 hours) | | `response.auth_token` | string | Signed JWT with `websocket` scope, bound to the requested profile | **Access Requirements:** | Profile Type | Permission Required | |--------------|-------------------| | User (self) | None beyond authentication | | Organization | `PERM_VIEW` on the org | | Workspace | `PERM_VIEW` on the workspace | | Share | `canViewShareDetails` | **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | No profile ID provided or invalid format | | `1605 (Invalid Input)` | 400 | Profile type unsupported, not found, or user lacks permissions | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT, or internal JWT generation failure | --- ### WebSocket Connection Connect to: `wss://{host}/api/websocket/?token={auth_token}` Where `{auth_token}` is the JWT returned from the auth endpoint above. ### WebSocket Message Types The server pushes two types of JSON messages: #### `activity` Messages Indicate which resource categories changed. Use these to know what to re-fetch: ```json { "response": "activity", "activity": ["storage:2abc...", "preview:2abc..."] } ``` The `activity` array contains the same activity key patterns as the polling endpoint. #### `event` Messages Sent alongside `activity` messages when structured event data is available. Provide full event details for immediate UI updates without a follow-up API call: ```json { "result": true, "response": "event", "time": "2026-03-22 14:30:45.123456 UTC", "timestamp": "1711123456.1234", "event": "workspace_storage_file_added", "category": "workspace", "subcategory": "storage", "object_id": "abc123def456ghi789jkl012mno345", "calling_user_id": "12345678901234567890", "activity_field": "storage", "data": { "name": "report.pdf", "parent_node_id": "xyz789...", "size": 1048576 } } ``` **`event` message fields:** `result` (boolean, always true), `response` (always `"event"`), `time` (server send time, `YYYY-MM-DD HH:MM:SS.ffffff UTC`, e.g. `"2026-03-22 14:30:45.123456 UTC"`), `timestamp` (event trigger time as microtime float string, e.g. `"1711123456.1234"`), `event` (event name), `category`, `subcategory`, `object_id` (affected object OpaqueId), `calling_user_id` (actor's 19-digit ID), `activity_field` (corresponding activity field name), `data` (event-specific details, shape varies by event type). **Backwards compatible:** `activity` messages are always sent. `event` messages are supplementary. Existing clients need no changes. Payload kept under ~4KB. **Permission gating:** Enriched `event` messages are only sent for member-level events. Admin and targeted events receive only the `activity` message. ### WebSocket Fallback If the WebSocket connection drops, fall back to long-polling (`GET /current/activity/poll/{profile_id}/`). Activity polling returns field names and timestamps. To retrieve full event details after reconnection, use `GET /current/events/search/` with a `created-min` filter. --- ## Realtime Auth (Collaborative Rooms) Separate from WebSocket activity channels, these endpoints provide authentication for collaborative editing rooms. --- ### `GET /current/realtime/auth/{room_id}` Generate a realtime JWT for a workspace or share collaborative room. Tokens are valid for 24 hours. **Auth:** Required (JWT). Not rate limited. No credit consumption. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{room_id}` | string | Yes | 19-digit numeric ID of the workspace or share to join | **curl Example:** ```bash curl -X GET "https://api.fast.io/current/realtime/auth/12345678901234567890" \ -H "Authorization: Bearer {jwt_token}" ``` **Response (200 OK):** ```json { "result": "yes", "response": { "expires_in": 86400, "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.expires_in` | integer | Token lifetime in seconds (86400 = 24 hours) | | `response.auth_token` | string | Signed JWT with `realtime` scope, bound to the requested room | **Access Requirements:** | Profile Type | Permission Required | |--------------|-------------------| | Workspace | At least `PERM_VIEW` | | Share | `canViewShareDetails` + multiplayer enabled (multiplayer status is automatically determined based on share configuration -- not directly togglable via API) | **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1605 (Invalid Input)` | 400 | Missing room ID | | `1605 (Invalid Input)` | 400 | Room ID is not numeric | | `1605 (Invalid Input)` | 400 | Room ID does not correspond to a workspace or share | | `1680 (Access Denied)` | 403 | User lacks permissions on the room | | `1650 (Authentication Invalid)` | 401 | Missing or invalid JWT, or internal JWT generation failure | **Notes:** - Only workspace and share profile types are accepted as room IDs. --- ### `GET /current/realtime/auth/validate/` Validate a realtime JWT and extract the room ID. Intended for backend services to verify tokens. **Auth:** Bearer token in Authorization header (the realtime JWT to validate, not a user JWT). **curl Example:** ```bash curl -X GET "https://api.fast.io/current/realtime/auth/validate/" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ``` **Response (200 OK):** ```json { "result": "yes", "response": { "room_id": "12345678901234567890" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `result` | string | `"yes"` on success | | `response.room_id` | string | The workspace or share profile ID the token is bound to | **Error Responses:** | Error Code | HTTP Status | Description | |------------|-------------|-------------| | `1650 (Authentication Invalid)` | 401 | Missing Authorization header | | `1605 (Invalid Input)` | 400 | Malformed Authorization header | | `1605 (Invalid Input)` | 400 | Authorization header does not use Bearer scheme | | `1605 (Invalid Input)` | 400 | Bearer keyword present but no token follows | | `1605 (Invalid Input)` | 400 | Token is not valid JWT format | | `1680 (Access Denied)` | 403 | Token signature verification or expiration check failed | | `1654 (Internal Error)` | 500 | Token payload is malformed or missing required fields | | `1605 (Invalid Input)` | 400 | Token scope is not `realtime` | **Notes:** - Only validates tokens with `realtime` scope. WebSocket-scoped tokens are rejected. - Validation is performed using the JWT alone. > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Comments API Base URL: `https://api.fast.io/current/` Auth: All endpoints require `Authorization: Bearer {jwt_token}`. Response format: JSON (standard envelope with `result`, `status`, and data fields). Content-Type: Comments use **JSON request bodies** (`Content-Type: application/json`), unlike most other Fast.io endpoints which use `application/x-www-form-urlencoded`. --- ## Endpoint Summary | Method | Path | Description | |--------|------|-------------| | GET | `/current/comments/{entity_type}/{parent_id}/` | List all comments in a workspace or share | | GET | `/current/comments/{entity_type}/{parent_id}/{node_id}/` | List comments for a specific node | | POST | `/current/comments/{entity_type}/{parent_id}/` | Create or update a comment on an entity | | POST | `/current/comments/{entity_type}/{parent_id}/{node_id}/` | Create or update a comment on a node | | GET | `/current/comments/{comment_id}/details/` | Get a single comment's details | | DELETE | `/current/comments/{comment_id}/delete/` | Soft-delete a comment and its replies | | POST | `/current/comments/bulk/delete/` | Bulk soft-delete multiple comments | | POST | `/current/comments/{comment_id}/reactions/` | Add or change an emoji reaction | | DELETE | `/current/comments/{comment_id}/reactions/` | Remove an emoji reaction | | POST | `/current/comments/{comment_id}/link/` | Link a comment to a workflow entity | | POST | `/current/comments/{comment_id}/unlink/` | Unlink a comment from a workflow entity | | GET | `/current/comments/linked/{entity_type}/{entity_id}/` | Get all comments linked to a workflow entity | --- ## Path Parameters Comments are scoped to a workspace or share, and optionally to a specific node (file or folder) within it. When no node ID is specified, all comments across the entire workspace or share are returned. | Parameter | Type | Format | Description | |-----------|------|--------|-------------| | `{entity_type}` | string | `"workspace"` or `"share"` | Entity type discriminator | | `{parent_id}` | string | 19-digit numeric | Workspace or share profile ID. **Note:** Do not confuse with `parent_id` in the POST request body, which is the parent *comment* ID for threading | | `{node_id}` | string | Alphanumeric opaque ID | File or folder ID within the entity (optional) | | `{comment_id}` | string | Alphanumeric opaque ID | Comment identifier | --- ## Comment Object Every comment returned by the API has this structure: | Field | Type | Description | |-------|------|-------------| | `id` | string | Alphanumeric opaque ID of the comment | | `entity_id` | string | Opaque ID of the entity (workspace, share, or node) | | `profile_id` | string | 19-digit numeric ID of the comment author | | `parent_id` | string\|null | Opaque ID of the parent comment (for replies), or null for top-level | | `body` | string | Comment text content (may include mention markup) | | `properties` | object | Metadata properties (includes `mentions`, `content_filtered` if applicable) | | `reference` | object\|null | Anchoring reference to a position in a file (see Reference Anchoring) | | `reactions` | object | Map of emoji character to total reaction count (e.g., `{"👍": 3}`) | | `user_reaction` | string\|null | The current authenticated user's emoji reaction, or null | | `created` | string | Creation timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Last-updated timestamp (`YYYY-MM-DD HH:MM:SS`) | | `deleted` | string\|null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null if active | --- ## List Comments ### `GET /current/comments/{entity_type}/{parent_id}/` ### `GET /current/comments/{entity_type}/{parent_id}/{node_id}/` Without `{node_id}`: returns all comments across the entire workspace or share. With `{node_id}`: returns only comments on that specific file or folder. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `sort` | string | No | `asc` | `"asc"` or `"desc"` | Sort order by creation time | | `limit` | integer | No | -- | Min: 2, Max: 200 | Number of comments to return | | `offset` | integer | No | `0` | Min: 0 | Number of comments to skip | | `page` | integer | No | -- | Min: 1 | Page number (alternative to offset-based pagination) | | `include_deleted` | boolean | No | `false` | -- | Include soft-deleted comments in results | | `reference_type` | string | No | -- | -- | Filter by reference anchor type (e.g., `"page"`, `"timestamp"`) | | `include_total` | boolean | No | `false` | -- | Include total count and pagination metadata in response | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/comments/workspace/1234567890123456789/?limit=50&include_total=true" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "comments": [ { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {"👍": 2, "❤️": 1}, "user_reaction": "👍", "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } ], "count": 1, "allowed": true, "remaining": 95, "total": 5, "limit": 50, "offset": 0 }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.comments` | array | Array of comment objects | | `response.count` | int | Number of comments in this response | | `response.allowed` | bool | Whether the current user can post new comments (based on plan limits) | | `response.remaining` | int | Remaining comments allowed under plan limit (only present if plan has a limit) | | `response.total` | int | Total number of comments (only if `include_total=true`) | | `response.limit` | int | Limit used in query (only if `include_total=true`) | | `response.offset` | int | Offset used in query (only if `include_total=true`) | **Access Levels:** | Role | Access | |------|--------| | Workspace Owner/Member | Full access -- sees all comments | | Share Owner | Full access -- sees all comments | | Share Guest | Filtered -- sees own comments; visibility of owner and other guest comments depends on share permissions | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Invalid API format..." | Missing entity type or parent ID | | `1605 (Invalid Input)` | 400 | "Entity type must be \"workspace\" or \"share\"" | Invalid entity type | | `1680 (Access Denied)` | 403 | "Invalid entity or insufficient permissions" | User lacks access | | `1609 (Not Found)` | 404 | "Invalid entity or insufficient permissions" | Entity not found | | `1654 (Internal Error)` | 500 | "Failed to retrieve comments" | Internal error | **Notes:** - **Scope-level queries:** When called without a `{node_id}`, this endpoint returns comments from all files and folders within the workspace or share. Only comments created after 2026-03-15 are included in scope-level results; older comments are accessible via the node-level endpoint. - For share entities, comments are filtered based on the user's share permissions. --- ## Create or Update Comment ### `POST /current/comments/{entity_type}/{parent_id}/` ### `POST /current/comments/{entity_type}/{parent_id}/{node_id}/` Create a new comment or update an existing one. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `body` | string | Yes | 1-8192 characters (raw); display text excluding mentions max 500 characters | Comment text content | | `comment_id` | string | No | Valid opaque ID | Include to update an existing comment; omit to create new | | `parent_id` | string | No | Valid opaque ID | Parent comment ID for threaded reply (single-level only). **Note:** This is the parent *comment's* opaque ID, not the workspace/share ID from the URL path `{parent_id}` | | `properties` | object | No | -- | Arbitrary key-value metadata to attach | | `reference` | object | No | See Reference Anchoring | Anchoring reference to a position in a file | **Request Example -- Create a new comment:** ```bash curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "body": "Great work on this document!" }' ``` **Request Example -- Reply to a comment:** ```bash curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "body": "Thanks for the feedback!", "parent_id": "abc123opaqueid" }' ``` **Request Example -- Comment with mention and page reference:** ```bash curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "body": "Hey @[user:9876543210987654321:Jane Smith], can you review page 3?", "reference": {"type": "page", "page": 3} }' ``` **Request Example -- Update an existing comment:** ```bash curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "comment_id": "def456opaqueid", "body": "Updated: This looks great after the revision." }' ``` **Response:** ```json { "result": "yes", "response": { "comment": { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "Great work on this document!", "properties": {}, "reference": null, "reactions": {}, "user_reaction": null, "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.comment` | object | The created or updated comment object (full schema above) | **Access Levels:** | Role | Access | |------|--------| | Workspace Owner/Member | Can create and edit own comments | | Share Owner | Can create and edit own comments | | Share Guest | Can only post if comments are enabled on the share | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Authentication required to post comments" | User not authenticated | | `1605 (Invalid Input)` | 400 | "Invalid JSON in request body" | Malformed JSON | | `1605 (Invalid Input)` | 400 | "Comment body must be between 1 and 8192 characters" | Body length out of range | | `1605 (Invalid Input)` | 400 | "Comment text (excluding mentions) must not exceed 500 characters" | Display text too long | | `1605 (Invalid Input)` | 400 | "Your comment appears to contain spam content..." | Spam detected | | `1605 (Invalid Input)` | 400 | "Invalid parent comment ID format" | Malformed parent_id | | `1609 (Not Found)` | 404 | "Parent comment not found" | Referenced parent does not exist | | `1605 (Invalid Input)` | 400 | "Parent comment belongs to different entity" | Parent is on a different entity | | `1605 (Invalid Input)` | 400 | "Invalid reference data: ..." | Reference failed validation | | `1605 (Invalid Input)` | 400 | "Comment limit exceeded for your plan" | Plan comment limit reached | | `1609 (Not Found)` | 404 | "Comment not found" | Comment ID for update not found | | `1680 (Access Denied)` | 403 | "You do not have permission to edit this comment" | User is not the comment author | | `1654 (Internal Error)` | 500 | "Failed to save comment" | Internal error | **Notes:** - **Content filtering:** Bodies are sanitized (HTML stripped) and filtered for profanity, spam, and PII. Spam is blocked. Other filtered content is modified and `content_filtered` is set in `properties`. - **Mentions:** Mentioned users are parsed and validated against entity membership. Valid mention profile IDs are stored in `properties.mentions`. - **Plan limits:** Number of comments per entity is limited by the organization's plan. - **Updating:** Only the original author can edit. Editing sets `edited_at` in properties. --- ## Get Comment Details ### `GET /current/comments/{comment_id}/details/` Get a single comment's full details. **Auth:** Required (JWT). Rate limited. **Request Example:** ```bash curl -X GET "https://api.fast.io/current/comments/abc123opaqueid/details/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "comment": { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {}, "user_reaction": null, "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist | | `1680 (Access Denied)` | 403 | "You do not have permission to view this comment" | User lacks access to the entity | --- ## Delete Comment ### `DELETE /current/comments/{comment_id}/delete/` Soft-delete a comment. **Recursive** -- also deletes all replies to this comment. **Auth:** Required (JWT). Rate limited. **Request Example:** ```bash curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/delete/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "message": "Comment deleted successfully", "comment": { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {}, "user_reaction": null, "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": "2025-01-16T08:00:00+00:00" } }, "current_api_version": "1.0" } ``` **Access Levels:** | Role | Access | |------|--------| | Comment Author | Can delete own comments | | Entity Owner | Can delete any comment on their entity | | Other Users | Denied | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist | | `1605 (Invalid Input)` | 400 | "Comment is already deleted" | Comment was previously deleted | | `1680 (Access Denied)` | 403 | "You do not have permission to delete this comment" | Not author or entity owner | | `1654 (Internal Error)` | 500 | "Failed to delete comment" | Internal error | --- ## Bulk Delete Comments ### `POST /current/comments/bulk/delete/` Bulk soft-delete multiple comments. Each comment is processed independently -- partial success is possible. **NOT recursive** -- does not delete replies. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `comment_ids` | array\ | Yes | Max 100 items | Array of comment opaque IDs to delete | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/comments/bulk/delete/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"comment_ids": ["abc123opaqueid", "def456opaqueid", "ghi789opaqueid"]}' ``` **Response:** ```json { "result": "yes", "response": { "success": true, "deleted_count": 3, "failed_count": 0, "total_count": 3, "results": { "abc123opaqueid": {"id": "abc123opaqueid", "success": true, "error": null}, "def456opaqueid": {"id": "def456opaqueid", "success": true, "error": null}, "ghi789opaqueid": {"id": "ghi789opaqueid", "success": true, "error": null} } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.success` | bool | `true` only if all comments were deleted (`failed_count === 0`) | | `response.deleted_count` | int | Number of successfully deleted comments | | `response.failed_count` | int | Number that failed to delete | | `response.total_count` | int | Total number of IDs provided | | `response.results` | object | Per-comment results keyed by comment ID | | `response.results.{id}.success` | bool | Whether this comment was deleted | | `response.results.{id}.error` | string\|null | Error message if failed, null on success | **Per-Comment Error Messages:** | Error | Cause | |-------|-------| | `"Invalid comment ID format"` | Not a valid opaque ID | | `"Comment not found"` | No comment with this ID | | `"Comment already deleted"` | Previously soft-deleted | | `"Permission denied"` | User is not author or entity owner | | `"Failed to delete comment"` | Internal error | **Request-Level Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Invalid JSON request body" | Malformed JSON | | `1605 (Invalid Input)` | 400 | "comment_ids array is required" | Missing or non-array field | | `1605 (Invalid Input)` | 400 | "comment_ids array cannot be empty" | Empty array | | `1605 (Invalid Input)` | 400 | "Maximum 100 comments can be deleted at once" | Exceeds bulk limit | **Important:** Unlike single delete, bulk delete does **not** recursively delete replies. --- ## Add or Change Reaction ### `POST /current/comments/{comment_id}/reactions/` Add an emoji reaction to a comment. One reaction per user per comment -- sending a new reaction replaces any previous one. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `emoji` | string | Yes | Single emoji character; max 2 UTF-8 characters; must match Unicode emoji ranges | Emoji to react with | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"emoji": "👍"}' ``` **Response:** ```json { "result": "yes", "response": { "reactions": {"👍": 3, "❤️": 1}, "user_reaction": "👍" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.reactions` | object | Map of emoji to total reaction count across all users | | `response.user_reaction` | string\|null | The current user's active reaction emoji | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist or is deleted | | `1680 (Access Denied)` | 403 | "You do not have permission to react to this comment" | User lacks entity access | | `1605 (Invalid Input)` | 400 | "emoji parameter is required" | Missing or non-string emoji | | `1605 (Invalid Input)` | 400 | "Invalid emoji character" | Does not match Unicode emoji pattern | | `1605 (Invalid Input)` | 400 | "Only single emoji allowed" | String exceeds 2 UTF-8 characters | | `1654 (Internal Error)` | 500 | "Failed to save reaction" | Internal error | --- ## Remove Reaction ### `DELETE /current/comments/{comment_id}/reactions/` Remove an emoji reaction from a comment. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `emoji` | string | No | Must match Unicode emoji ranges if provided | Specific emoji to remove. Omit to remove any reaction by the current user | **Request Example -- Remove specific emoji:** ```bash curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"emoji": "👍"}' ``` **Request Example -- Remove any reaction:** ```bash curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response:** ```json { "result": "yes", "response": { "reactions": {"❤️": 1}, "user_reaction": null }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.reactions` | object | Updated map of emoji to total reaction count | | `response.user_reaction` | string\|null | Current user's reaction after removal (null if removed) | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist or is deleted | | `1680 (Access Denied)` | 403 | "You do not have permission to react to this comment" | User lacks entity access | | `1605 (Invalid Input)` | 400 | "emoji must be a string" | Non-string emoji parameter | | `1605 (Invalid Input)` | 400 | "Invalid emoji character" | Does not match Unicode emoji pattern | | `1654 (Internal Error)` | 500 | "Failed to remove reaction" | Internal error | **Notes:** - Removing a reaction that does not exist returns success (idempotent). - A "removed" event is triggered only when a reaction was actually removed. --- ## Link Comment to Workflow Entity ### `POST /current/comments/{comment_id}/link/` Link a comment to a workflow entity such as a task or approval. This creates an association between the comment and the target entity, enabling reverse lookups and workflow integration. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `entity_type` | string | Yes | `"task"` or `"approval"` | The type of workflow entity to link to | | `entity_id` | string | Yes | Valid alphanumeric opaque ID | The ID of the workflow entity to link to | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/link/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "entity_type": "task", "entity_id": "task789opaqueid" }' ``` **Response:** ```json { "result": "yes", "response": { "comment": { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {}, "user_reaction": null, "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.comment` | object | The updated comment object with the link applied | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Authentication required to link comments" | User not authenticated | | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID in path | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed comment opaque ID | | `1605 (Invalid Input)` | 400 | "Invalid JSON in request body" | Malformed JSON | | `1605 (Invalid Input)` | 400 | "entity_type is required" | Missing or non-string entity_type | | `1605 (Invalid Input)` | 400 | "entity_type must be one of: task, approval" | Invalid entity type | | `1605 (Invalid Input)` | 400 | "entity_id is required" | Missing or non-string entity_id | | `1605 (Invalid Input)` | 400 | "Invalid entity_id format" | Malformed entity opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist | | `1680 (Access Denied)` | 403 | "You do not have permission to link this comment" | User lacks access to the comment's entity | | `1609 (Not Found)` | 404 | "Target entity not found" | The workflow entity to link to does not exist | | `1605 (Invalid Input)` | 400 | "Invalid link parameters" | Link validation failed | | `1654 (Internal Error)` | 500 | "Failed to link comment" | Internal error | --- ## Unlink Comment from Workflow Entity ### `POST /current/comments/{comment_id}/unlink/` Remove the link between a comment and its associated workflow entity. After unlinking, the comment still exists but is no longer associated with any workflow entity. **Auth:** Required (JWT). Rate limited. **Content-Type:** `application/json` **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `entity_id` | string | No | Valid alphanumeric opaque ID | The ID of the linked entity (used for cache invalidation; recommended but not required) | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/unlink/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{ "entity_id": "task789opaqueid" }' ``` **Request Example -- Without entity_id:** ```bash curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/unlink/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response:** ```json { "result": "yes", "response": { "comment": { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {}, "user_reaction": null, "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.comment` | object | The updated comment object with the link removed | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1680 (Access Denied)` | 403 | "Authentication required to unlink comments" | User not authenticated | | `1605 (Invalid Input)` | 400 | "Comment ID is required" | Missing comment ID in path | | `1605 (Invalid Input)` | 400 | "Invalid comment ID format" | Malformed comment opaque ID | | `1609 (Not Found)` | 404 | "Comment not found" | Comment does not exist | | `1680 (Access Denied)` | 403 | "You do not have permission to unlink this comment" | User lacks access to the comment's entity | | `1605 (Invalid Input)` | 400 | "Comment is not linked to any entity" | Comment has no existing link | | `1654 (Internal Error)` | 500 | "Failed to unlink comment" | Internal error | --- ## Get Linked Comments ### `GET /current/comments/linked/{entity_type}/{entity_id}/` Reverse lookup: retrieve all comments that have been linked to a specific workflow entity (task or approval). **Auth:** Required (JWT). Rate limited. **Path Parameters:** | Parameter | Type | Format | Description | |-----------|------|--------|-------------| | `{entity_type}` | string | `"task"` or `"approval"` | The type of workflow entity | | `{entity_id}` | string | Alphanumeric opaque ID | The ID of the workflow entity | **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | integer | No | `50` | Min: 1, Max: 200 | Number of comments to return | | `offset` | integer | No | `0` | Min: 0 | Number of comments to skip | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/comments/linked/task/task789opaqueid/?limit=25&offset=0" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "comments": [ { "id": "abc123opaqueid", "entity_id": "xyz789opaqueid", "profile_id": "1234567890123456789", "parent_id": null, "body": "This looks great!", "properties": {}, "reference": null, "reactions": {"👍": 2}, "user_reaction": "👍", "created": "2025-01-15T10:30:00+00:00", "updated": "2025-01-15T10:30:00+00:00", "deleted": null } ], "count": 1, "entity_type": "task", "entity_id": "task789opaqueid", "limit": 25, "offset": 0 }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.comments` | array | Array of comment objects linked to the entity | | `response.count` | int | Number of comments in this response | | `response.entity_type` | string | The entity type that was queried | | `response.entity_id` | string | The entity ID that was queried | | `response.limit` | int | Limit used in the query | | `response.offset` | int | Offset used in the query | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "entity_type is required in URL path" | Missing entity type | | `1605 (Invalid Input)` | 400 | "entity_type must be one of: task, approval" | Invalid entity type | | `1605 (Invalid Input)` | 400 | "entity_id is required in URL path" | Missing entity ID | | `1605 (Invalid Input)` | 400 | "Invalid entity_id format" | Malformed opaque ID | | `1654 (Internal Error)` | 500 | "Failed to retrieve linked comments" | Internal error | --- ## Threading Comments support **single-level threading only**. - Set `parent_id` in the request body to the opaque ID of the comment you are replying to. This is distinct from `{parent_id}` in the URL path, which identifies the workspace or share container. - Replies to a top-level comment appear as children of that comment. - Replies to a reply are **auto-flattened** -- they become siblings of the original reply (children of the same top-level parent). The API does not support nested threading beyond one level. ``` Comment A (top-level) ├── Comment B (reply to A) ├── Comment C (reply to B → auto-flattened to reply to A) └── Comment D (reply to A) ``` --- ## Mentions Use mention markup in the `body` field to tag other users: ``` @[user:USER_ID:Display Name] ``` | Component | Description | |-----------|-------------| | `user` | Literal prefix (always `user`) | | `USER_ID` | The user's 19-digit numeric profile ID | | `Display Name` | Display name to show in the UI | **Character limit details:** - The **full mention tag markup** (e.g., `@[user:1234567890123456789:Jane Smith]`) counts toward the 8192-character body limit. - Display text (excluding all mention markup) is separately limited to 500 characters. - Mentioned users are validated against entity membership. Valid mentions are stored in `properties.mentions`. **Example body with mentions:** ``` Hey @[user:1234567890123456789:Jane Smith] and @[user:9876543210987654321:Bob Jones], please review this file. ``` --- ## Reference Anchoring Comments can be anchored to specific positions within a file using the `reference` object. **Reference object fields:** | Field | Type | Description | |-------|------|-------------| | `type` | string | Required. The anchor type: `"page"`, `"timestamp"`, `"region"`, `"text"` | | `timestamp` | number | For video/audio -- position in seconds | | `page` | integer | For documents -- page number | | `region` | object | For images -- coordinates `{x, y, width, height}` defining a rectangular area | | `text_snippet` | string | For text files -- the referenced text passage | **Example -- Video timestamp:** ```json { "body": "The transition at this point needs work.", "reference": {"type": "timestamp", "timestamp": 42.5} } ``` **Example -- Document page:** ```json { "body": "Typo in the second paragraph.", "reference": {"type": "page", "page": 7} } ``` **Example -- Image region:** ```json { "body": "This area needs higher contrast.", "reference": {"type": "region", "region": {"x": 120, "y": 80, "width": 200, "height": 150}} } ``` **Example -- Text snippet:** ```json { "body": "This sentence should be rephrased.", "reference": {"type": "text", "text_snippet": "The quick brown fox jumps over the lazy dog."} } ``` --- ## Content Filtering Comment bodies undergo automatic filtering: - **HTML sanitization:** All HTML tags are stripped. - **Spam detection:** Comments identified as spam are **blocked** entirely (400 error). - **Profanity/PII filtering:** Detected content is modified in-place and `properties.content_filtered` is set to `true`. > Part of the Fast.io API Reference. Overview: https://api.fast.io/current/llms/ # Workflow (Task Management) API Base URL: `https://api.fast.io/current/` Auth: All endpoints require `Authorization: Bearer {jwt_token}`. Response format: JSON (standard envelope with `result`, `status`, and data fields). Feature gate: All workflow endpoints (except toggle) require the `workflow` feature to be enabled on the target workspace or share. Share permissions: On shares, only approvals are accessible to guests. Guests see only approvals assigned to them (where approver_id matches their user ID) and can resolve those approvals. Tasks, todos, and worklogs require member or admin access -- guests are blocked. Public guests remain blocked from everything. Toggle endpoints (enable/disable) remain Admin-only. Markdown output: Workflow GET endpoints support `format=md` query parameter for Markdown output (AI agent-friendly). --- ## Endpoint Summary ### Feature Toggle | Method | Path | Description | |--------|------|-------------| | POST | `/current/workspace/{workspace_id}/workflow/enable/` | Enable workflow on a workspace | | POST | `/current/workspace/{workspace_id}/workflow/disable/` | Disable workflow on a workspace | | POST | `/current/share/{share_id}/workflow/enable/` | Enable workflow on a share | | POST | `/current/share/{share_id}/workflow/disable/` | Disable workflow on a share | ### Task Lists & Tasks | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/tasks/` | List task lists in a workspace | | GET | `/current/share/{share_id}/tasks/` | List task lists in a share | | POST | `/current/workspace/{workspace_id}/tasks/create/` | Create a task list in a workspace | | POST | `/current/share/{share_id}/tasks/create/` | Create a task list in a share | | GET | `/current/tasks/{list_id}/details/` | Get task list details (includes tasks) | | POST | `/current/tasks/{list_id}/update/` | Update a task list | | POST | `/current/tasks/{list_id}/delete/` | Soft-delete a task list | | GET | `/current/tasks/{list_id}/items/` | List tasks in a task list | | POST | `/current/tasks/{list_id}/items/create/` | Create a task | | GET | `/current/tasks/{list_id}/items/{task_id}/` | Get task details | | POST | `/current/tasks/{list_id}/items/{task_id}/update/` | Update a task | | POST | `/current/tasks/{list_id}/items/{task_id}/delete/` | Soft-delete a task | | POST | `/current/tasks/{list_id}/items/{task_id}/status/` | Change task status | | POST | `/current/tasks/{list_id}/items/{task_id}/assign/` | Assign/unassign a task | | POST | `/current/tasks/{list_id}/items/{task_id}/move/` | Move task to another list | | POST | `/current/tasks/{list_id}/items/bulk-status/` | Bulk update task statuses | | POST | `/current/tasks/{list_id}/items/reorder/` | Bulk reorder tasks in a task list | | POST | `/current/workspace/{workspace_id}/tasks/reorder/` | Bulk reorder task lists in a workspace | | POST | `/current/share/{share_id}/tasks/reorder/` | Bulk reorder task lists in a share | | GET | `/current/workspace/{workspace_id}/tasks/list/{filter}/` | Filtered task list for workspace (personal view) | | GET | `/current/share/{share_id}/tasks/list/{filter}/` | Filtered task list for share (group view, members/admins only) | | GET | `/current/workspace/{workspace_id}/tasks/summary/` | Task count summary for workspace | | GET | `/current/share/{share_id}/tasks/summary/` | Task count summary for share (members/admins only) | ### Worklogs | Method | Path | Description | |--------|------|-------------| | GET | `/current/worklogs/{entity_type}/{entity_id}/` | List worklog entries | | POST | `/current/worklogs/{entity_type}/{entity_id}/append/` | Append a worklog entry | | POST | `/current/worklogs/{entity_type}/{entity_id}/interjection/` | Create an interjection | | GET | `/current/worklogs/{entity_type}/{entity_id}/interjections/` | List unacknowledged interjections | | GET | `/current/worklogs/{entry_id}/details/` | Get entry details | | POST | `/current/worklogs/{entry_id}/acknowledge/` | Acknowledge an interjection | | GET | `/current/workspace/{workspace_id}/worklogs/` | List all worklog entries in a workspace | | GET | `/current/share/{share_id}/worklogs/` | List all worklog entries in a share (members/admins only) | | GET | `/current/workspace/{workspace_id}/worklogs/list/{filter}/` | Filtered worklog list for workspace (personal view) | | GET | `/current/share/{share_id}/worklogs/list/{filter}/` | Filtered worklog list for share (group view, members/admins only) | | GET | `/current/workspace/{workspace_id}/worklogs/summary/` | Worklog entry summary for workspace | | GET | `/current/share/{share_id}/worklogs/summary/` | Worklog entry summary for share (members/admins only) | ### Approvals | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/approvals/` | List approvals in a workspace | | GET | `/current/share/{share_id}/approvals/` | List approvals in a share | | POST | `/current/approvals/{entity_type}/{entity_id}/create/` | Create an approval request | | GET | `/current/approvals/{approval_id}/details/` | Get approval details | | POST | `/current/approvals/{approval_id}/resolve/` | Resolve an approval | | POST | `/current/approvals/{approval_id}/update/` | Update a pending approval | | POST | `/current/approvals/{approval_id}/delete/` | Delete an approval | | POST | `/current/approvals/bulk/{action}/` | Bulk create, approve, reject, or delete approvals in a share | | GET | `/current/user/approvals/list/{filter}/` | List approvals for the authenticated user by filter | | GET | `/current/workspace/{workspace_id}/approvals/list/{filter}/` | Filtered approval list for workspace (personal view) | | GET | `/current/share/{share_id}/approvals/list/{filter}/` | Filtered approval list for share (group view for owners, scoped to assigned for guests) | | GET | `/current/workspace/{workspace_id}/approvals/list/summary/` | Approval count summary for workspace | | GET | `/current/share/{share_id}/approvals/list/summary/` | Approval count summary for share (owners see all + assigned_to_me, guests see assigned_to_me only) | ### Todos | Method | Path | Description | |--------|------|-------------| | GET | `/current/workspace/{workspace_id}/todos/` | List todos in a workspace | | GET | `/current/share/{share_id}/todos/` | List todos in a share | | POST | `/current/workspace/{workspace_id}/todos/create/` | Create a todo in a workspace | | POST | `/current/share/{share_id}/todos/create/` | Create a todo in a share | | GET | `/current/todos/{todo_id}/details/` | Get todo details | | POST | `/current/todos/{todo_id}/details/update/` | Update a todo | | POST | `/current/todos/{todo_id}/details/delete/` | Soft-delete a todo | | POST | `/current/todos/{todo_id}/details/toggle/` | Toggle todo done state | | POST | `/current/workspace/{workspace_id}/todos/bulk-toggle/` | Bulk toggle todos in a workspace | | POST | `/current/share/{share_id}/todos/bulk-toggle/` | Bulk toggle todos in a share | | GET | `/current/workspace/{workspace_id}/todos/list/{filter}/` | Filtered todo list for workspace (personal view) | | GET | `/current/share/{share_id}/todos/list/{filter}/` | Filtered todo list for share (group view, members/admins only) | | GET | `/current/workspace/{workspace_id}/todos/summary/` | Todo count summary for workspace | | GET | `/current/share/{share_id}/todos/summary/` | Todo count summary for share (members/admins only) | --- ## Common Path Parameters | Parameter | Type | Format | Description | |-----------|------|--------|-------------| | `{workspace_id}` | string | 19-digit numeric | Workspace profile ID | | `{share_id}` | string | 19-digit numeric | Share profile ID | | `{list_id}` | string | 30-character alphanumeric | Task list ID | | `{task_id}` | string | 30-character alphanumeric | Task ID | | `{entry_id}` | string | 30-character alphanumeric | Worklog entry ID | | `{approval_id}` | string | 30-character alphanumeric | Approval ID | | `{todo_id}` | string | 30-character alphanumeric | Todo ID | | `{entity_type}` | string | See per-section enums | Entity type discriminator | | `{entity_id}` | string | Varies by entity type | Entity identifier | | `{filter}` | string | `pending`, `created`, `resolved` | User approvals filter | --- ## Common Error Responses These errors apply to all workflow endpoints (except toggle): | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid JWT token | | `1680 (Access Denied)` | 403 | "Workflow is not enabled" | Workflow feature not enabled on entity | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | User lacks required access level | | `1651 (Invalid Request Type)` | 400 | "Invalid request" | Wrong HTTP method | | `1671 (Rate Limited)` | 429 | "Rate limit exceeded" | Too many requests | --- ## Feature Toggle Enable or disable workflow on a workspace or share. Requires admin-level access. These endpoints do **not** require workflow to already be enabled. ### `POST /current/workspace/{workspace_id}/workflow/enable/` Enable workflow features on a workspace. **Auth:** Required (JWT). Admin or owner. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/workflow/enable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "message": "Workflow features enabled", "workflow": true }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.message` | string | Confirmation message | | `response.workflow` | bool | `true` = enabled, `false` = disabled | **Access Levels:** | Role | Access | |------|--------| | Owner / Admin | Can enable/disable | | Member / Guest | Denied | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1609 (Not Found)` | 404 | "Workspace not found" | Workspace does not exist | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | User is not owner or admin | | `1605 (Invalid Input)` | 400 | "Workflow is already enabled" | Already enabled | | `1654 (Internal Error)` | 500 | "Failed to enable workflow" | Internal error | ### `POST /current/workspace/{workspace_id}/workflow/disable/` Disable workflow on a workspace. Existing data is preserved but inaccessible until re-enabled. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/workflow/disable/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "message": "Workflow features disabled", "workflow": false }, "current_api_version": "1.0" } ``` **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1650 (Authentication Invalid)` | 401 | "Authentication required" | Missing or invalid token | | `1609 (Not Found)` | 404 | "Workspace not found" | Workspace does not exist | | `1680 (Access Denied)` | 403 | "Insufficient permissions" | User is not owner or admin | | `1605 (Invalid Input)` | 400 | "Workflow is already disabled" | Already disabled | | `1654 (Internal Error)` | 500 | "Failed to disable workflow" | Internal error | ### Share Toggle Endpoints `POST /current/share/{share_id}/workflow/enable/` and `POST /current/share/{share_id}/workflow/disable/` behave identically to the workspace variants above, using `{share_id}` instead. **Notes:** - Disabling workflow does **not** delete any data. All entities are preserved and become accessible again when re-enabled. --- ## Task Lists & Tasks Task lists are containers for tasks, scoped to a workspace or share. Tasks support statuses, priorities, assignees, dependencies, and file/folder node links. ### Task Status Values | Value | Description | |-------|-------------| | `"pending"` | Not started | | `"in_progress"` | Actively being worked on | | `"complete"` | Finished | | `"blocked"` | Blocked by a dependency or issue | ### Valid Status Transitions | From | Allowed Transitions | |------|---------------------| | `pending` | `in_progress`, `blocked` | | `in_progress` | `complete`, `blocked`, `pending` | | `complete` | `pending`, `in_progress` | | `blocked` | `pending`, `in_progress` | ### Task Priority (integer) | Value | Meaning | |-------|---------| | `0` | None (default) | | `1` | Low | | `2` | Medium | | `3` | High | | `4` | Critical | ### Task List Object | Field | Type | Description | |-------|------|-------------| | `id` | string | 30-character alphanumeric ID | | `profile_id` | string | Workspace or share profile ID (19-digit numeric) | | `name` | string | Task list name | | `description` | string\|null | Task list description | | `created_by` | string | User profile ID of the creator | | `properties` | object | Metadata properties (JSON) | | `sort_order` | int | Display sort order | | `created` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `deleted` | string\|null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null | ### Task Object | Field | Type | Description | |-------|------|-------------| | `id` | string | 30-character alphanumeric ID | | `task_list_id` | string | Parent task list ID | | `profile_id` | string | Workspace or share profile ID (19-digit numeric) | | `created_by` | string | User profile ID of the task creator | | `title` | string | Task title | | `description` | string\|null | Task description | | `status` | string | `"pending"`, `"in_progress"`, `"complete"`, `"blocked"` | | `assignee_id` | string\|null | Assigned user's profile ID, or null | | `priority` | int 0 (none) through 4 (critical) | | `sort_order` | int | Display sort order | | `dependencies` | array\\|null | Task IDs this task depends on | | `node_id` | string\|null | Associated file/folder node ID | | `properties` | object | Metadata properties (JSON) | | `created` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `deleted` | string\|null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null | --- ### List Task Lists #### `GET /current/workspace/{workspace_id}/tasks/` #### `GET /current/share/{share_id}/tasks/` List all task lists in a workspace or share. > **Backwards compatibility:** The previous routes (`/current/tasks/workspace/{workspace_id}/` and `/current/tasks/share/{share_id}/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 200 | Number of task lists to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `format` | string | No | -- | `"md"` | Markdown output | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/tasks/?limit=50" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "task_lists": [ { "id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5", "profile_id": "1234567890123456789", "name": "Sprint 12 Tasks", "description": "Tasks for the current sprint", "created_by": "9876543210987654321", "properties": {}, "sort_order": 0, "created": "2025-06-01T09:00:00+00:00", "updated": "2025-06-10T14:30:00+00:00", "deleted": null } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` --- ### Create Task List #### `POST /current/workspace/{workspace_id}/tasks/create/` #### `POST /current/share/{share_id}/tasks/create/` Create a new task list. > **Backwards compatibility:** The previous routes (`/current/tasks/workspace/{workspace_id}/create/` and `/current/tasks/share/{share_id}/create/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `name` | string | Yes | 1-255 characters | Task list name | | `description` | string | No | Max 65535 characters | Task list description | | `properties` | object | No | -- | Metadata properties | | `sort_order` | int | No | Default 0 | Display sort order | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/tasks/create/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "name=Sprint%2012%20Tasks&description=Tasks%20for%20the%20current%20sprint" ``` **Response:** ```json { "result": "yes", "response": { "task_list": { "id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5", "profile_id": "1234567890123456789", "name": "Sprint 12 Tasks", "description": "Tasks for the current sprint", "created_by": "9876543210987654321", "properties": {}, "sort_order": 0, "created": "2025-06-01T09:00:00+00:00", "updated": "2025-06-01T09:00:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` --- ### Get Task List Details #### `GET /current/tasks/{list_id}/details/` Get task list details including all tasks in the list. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `format` | string | No | -- | `"md"` for Markdown output | **Response:** ```json { "result": "yes", "response": { "task_list": { "id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5", "profile_id": "1234567890123456789", "name": "Sprint 12 Tasks", "description": "Tasks for the current sprint", "created_by": "9876543210987654321", "properties": {}, "sort_order": 0, "created": "2025-06-01T09:00:00+00:00", "updated": "2025-06-10T14:30:00+00:00", "deleted": null }, "tasks": [ { "id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "task_list_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5", "profile_id": "1234567890123456789", "title": "Implement login flow", "description": "Build the OAuth2 login flow", "status": "in_progress", "assignee_id": "9876543210987654321", "priority": 3, "sort_order": 0, "dependencies": null, "node_id": null, "properties": {}, "created": "2025-06-01T09:30:00+00:00", "updated": "2025-06-10T14:30:00+00:00", "deleted": null } ] }, "current_api_version": "1.0" } ``` --- ### Update Task List #### `POST /current/tasks/{list_id}/update/` Update a task list. Only provided fields are changed. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `name` | string | No | 1-255 characters | Updated name | | `description` | string\|null | No | Max 65535 chars; `null` to clear | Updated description | | `properties` | object | No | -- | Updated properties | | `sort_order` | int | No | -- | Updated sort order | --- ### Delete Task List #### `POST /current/tasks/{list_id}/delete/` Soft-delete a task list. **Auth:** Required (JWT). Rate limited. --- ### List Tasks #### `GET /current/tasks/{list_id}/items/` List all tasks in a task list. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 200 | Number of tasks to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `format` | string | No | -- | `"md"` | Markdown output | --- ### Create Task #### `POST /current/tasks/{list_id}/items/create/` Create a new task in a task list. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `title` | string | Yes | 1-512 characters | Task title | | `description` | string | No | Max 65535 characters | Task description | | `priority` | int | No | 0-4 (default 0) | Priority: 0=none, 1=low, 2=medium, 3=high, 4=critical | | `assignee_id` | string | No | 19-digit numeric profile ID | User to assign | | `node_id` | string | No | Alphanumeric opaque ID | File/folder node to link | | `dependencies` | array\ | No | Array of task IDs | Tasks this task depends on | | `properties` | object | No | -- | Metadata properties | | `sort_order` | int | No | Default 0 | Display sort order | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/tasks/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5/items/create/" \ -H "Authorization: Bearer {jwt_token}" \ -d "title=Implement%20login%20flow&priority=3&assignee_id=9876543210987654321" ``` **Response:** ```json { "result": "yes", "response": { "task": { "id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "task_list_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5", "profile_id": "1234567890123456789", "title": "Implement login flow", "description": null, "status": "pending", "assignee_id": "9876543210987654321", "priority": 3, "sort_order": 0, "dependencies": null, "node_id": null, "properties": {}, "created": "2025-06-01T09:30:00+00:00", "updated": "2025-06-01T09:30:00+00:00", "deleted": null } }, "current_api_version": "1.0" } ``` --- ### Get Task Details #### `GET /current/tasks/{list_id}/items/{task_id}/` Get full details of a single task. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `format` | string | No | -- | `"md"` for Markdown output | --- ### Update Task #### `POST /current/tasks/{list_id}/items/{task_id}/update/` Update a task. Only provided fields are changed. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `title` | string | No | 1-512 characters | Updated title | | `description` | string\|null | No | Max 65535 chars; `null` to clear | Updated description | | `priority` | int | No | 0-4 | Updated priority | | `assignee_id` | string\|null | No | 19-digit numeric; `null` to unassign | Updated assignee | | `node_id` | string\|null | No | Opaque ID; `null` to clear | Updated node link | | `dependencies` | array\ | No | Array of task IDs | Updated dependencies | | `properties` | object | No | -- | Updated properties | | `sort_order` | int | No | -- | Updated sort order | --- ### Delete Task #### `POST /current/tasks/{list_id}/items/{task_id}/delete/` Soft-delete a task. **Auth:** Required (JWT). Rate limited. --- ### Change Task Status #### `POST /current/tasks/{list_id}/items/{task_id}/status/` Change a task's status. Enforces valid transitions (see table above). **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `status` | string | Yes | `"pending"`, `"in_progress"`, `"complete"`, `"blocked"` | New status | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/tasks/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5/items/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/status/" \ -H "Authorization: Bearer {jwt_token}" \ -d "status=in_progress" ``` **Response:** ```json { "result": "yes", "response": { "task": { "id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "status": "in_progress", "...": "..." }, "old_status": "pending", "new_status": "in_progress" }, "current_api_version": "1.0" } ``` --- ### Assign Task #### `POST /current/tasks/{list_id}/items/{task_id}/assign/` Assign or unassign a task. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `assignee_id` | string\|null | Yes | 19-digit numeric profile ID, or `null` | User to assign, or `null` to unassign | **Notes:** - The `assignee_id` can refer to a pending member (an invited user who has not yet signed up). Pending members have a valid user ID and can be assigned to tasks before they create their account. When the user claims their account, the assignment is preserved. --- ### Move Task #### `POST /current/tasks/{list_id}/items/{task_id}/move/` Move a task from one task list to another within the same profile. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `target_task_list_id` | string | Yes | 30-char opaque ID | Destination task list ID | | `sort_order` | int | No | Default 0 | Sort order in the target list | **Constraints:** Target list must exist, not be deleted, and belong to the same profile. Source and target list cannot be the same. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/tasks/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5/items/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/move/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"target_task_list_id": "p1q2r3s4t5u6v7w8x9y0z1a2b3c4d5"}' ``` **Response:** Updated task object with `old_task_list_id` and `new_task_list_id`. --- ### Bulk Update Task Status #### `POST /current/tasks/{list_id}/items/bulk-status/` Update the status of multiple tasks at once. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `tasks` | array | Yes | Max 100 items | Array of `{"task_id": string, "status": string}` objects | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/tasks/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5/items/bulk-status/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"tasks": [{"task_id": "x1y2z3...", "status": "complete"}, {"task_id": "p1q2r3...", "status": "in_progress"}]}' ``` **Response:** ```json { "result": "yes", "response": { "success": true, "updated_count": 2, "failed_count": 0, "total_count": 2, "results": [ {"task_id": "x1y2z3...", "success": true, "error": null}, {"task_id": "p1q2r3...", "success": true, "error": null} ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.success` | bool | `true` if all tasks updated successfully | | `response.updated_count` | int | Number successfully updated | | `response.failed_count` | int | Number that failed | | `response.total_count` | int | Total number of tasks in request | | `response.results` | array | Per-task results with `task_id`, `success`, and `error` | --- ### Reorder Tasks #### `POST /current/tasks/{list_id}/items/reorder/` Bulk reorder tasks within a task list. Updates `sort_order` for each specified task atomically. **Auth:** Required (JWT). Rate limited. **Request Body (JSON):** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `order` | array | Yes | Non-empty array | Array of `{"id": string, "sort_order": int}` objects | Each entry in the `order` array specifies a task ID and its new sort order. All task IDs must belong to the specified task list. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/tasks/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5/items/reorder/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"order": [{"id": "x1y2z3...", "sort_order": 0}, {"id": "p1q2r3...", "sort_order": 1}]}' ``` **Response:** ```json { "result": "yes", "response": { "reordered": 2, "list_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.reordered` | int | Number of tasks reordered | | `response.list_id` | string | The task list ID | **Error Responses:** | Error Code | HTTP Status | Message | Cause | |------------|-------------|---------|-------| | `1605 (Invalid Input)` | 400 | "order must be a non-empty array..." | Missing or invalid order array | | `1605 (Invalid Input)` | 400 | "Task ID ... does not belong to this task list" | Task not in specified list | | `1654 (Internal Error)` | 500 | "Concurrent operation conflict..." | Concurrent reorder conflict | --- ### Reorder Task Lists #### `POST /current/workspace/{workspace_id}/tasks/reorder/` #### `POST /current/share/{share_id}/tasks/reorder/` Bulk reorder task lists within a workspace or share. Updates `sort_order` for each specified task list atomically. > **Backwards compatibility:** The previous routes (`/current/tasks/workspace/{workspace_id}/reorder/` and `/current/tasks/share/{share_id}/reorder/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Request Body (JSON):** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `order` | array | Yes | Non-empty array | Array of `{"id": string, "sort_order": int}` objects | Each entry in the `order` array specifies a task list ID and its new sort order. All task list IDs must belong to the specified workspace or share. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/tasks/reorder/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"order": [{"id": "a1b2c3...", "sort_order": 0}, {"id": "d4e5f6...", "sort_order": 1}]}' ``` **Response:** ```json { "result": "yes", "response": { "reordered": 2, "profile_id": "1234567890123456789" }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.reordered` | int | Number of task lists reordered | | `response.profile_id` | string | The workspace or share profile ID | --- ## Worklogs Worklogs provide an append-only activity stream attached to entities. Entries are immutable after creation. Interjections are urgent entries that require acknowledgement. ### Worklog Entity Types | Value | Description | |-------|-------------| | `"task"` | Attached to a task | | `"task_list"` | Attached to a task list | | `"node"` | Attached to a file/folder | | `"profile"` | Attached to a workspace or share | ### Entry Types | Value | Description | |-------|-------------| | `"info"` | General information | | `"decision"` | A decision record | | `"error"` | An error record | | `"status_change"` | A status change | | `"request"` | A request for action | | `"interjection"` | Urgent correction requiring acknowledgement (created via interjection endpoint) | ### Priority | Value | Description | |-------|-------------| | `"normal"` | Default priority | | `"urgent"` | Urgent (auto-set for interjections) | ### Worklog Entry Object | Field | Type | Description | |-------|------|-------------| | `id` | string | 30-character alphanumeric ID | | `entity_type` | string | `"task"`, `"task_list"`, `"node"`, `"profile"` | | `entity_id` | string | ID of the attached entity | | `profile_id` | string | Workspace or share profile ID (19-digit numeric) | | `author_id` | string | Author's user profile ID | | `entry_type` | string | Entry type (see enum above) | | `content` | string | Entry text content | | `priority` | string | `"normal"` or `"urgent"` | | `target_id` | string\|null | Target user profile ID (for interjections/requests) | | `acknowledged` | bool | `true` = acknowledged, `false` = not acknowledged | | `acknowledged_at` | string\|null | Acknowledgment timestamp (`YYYY-MM-DD HH:MM:SS`), or null | | `acknowledgable` | bool | `true` if entry is an unacknowledged interjection | | `properties` | object\|null | Metadata properties (JSON) | | `created` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Last-updated timestamp (`YYYY-MM-DD HH:MM:SS`), falls back to `created` if not set | Worklog entries are **immutable** after creation. The only mutable operation is acknowledging an interjection, which sets `acknowledged`, `acknowledged_at`, and `updated`. --- ### List Worklog Entries #### `GET /current/worklogs/{entity_type}/{entity_id}/` List worklog entries for an entity, ordered chronologically. **Auth:** Required (JWT). Rate limited. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{entity_type}` | string | Yes | `"task"`, `"task_list"`, `"node"`, `"profile"` | | `{entity_id}` | string | Yes | Entity ID | **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 200 | Number of entries to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `sort` | string | No | `"asc"` | `"asc"` or `"desc"` | Sort direction by creation time | | `format` | string | No | -- | `"md"` | Markdown output | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/worklogs/task/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/?limit=20&sort=desc" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "entries": [ { "id": "w1x2y3z4a5b6c7d8e9f0g1h2i3j4k5", "entity_type": "task", "entity_id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "profile_id": "1234567890123456789", "author_id": "9876543210987654321", "entry_type": "info", "content": "Completed initial review of uploaded documents.", "priority": "normal", "target_id": null, "acknowledged": false, "acknowledgable": false, "acknowledged_at": null, "properties": null, "created": "2025-06-10T14:30:00+00:00", "updated": "2025-06-10T14:30:00+00:00" } ], "pagination": { "limit": 20, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` --- ### Append Worklog Entry #### `POST /current/worklogs/{entity_type}/{entity_id}/append/` Append a standard entry to the worklog. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `content` | string | Yes | 1-65535 characters | Entry text content | | `entry_type` | string | No | `"info"` (default), `"decision"`, `"error"`, `"status_change"`, `"request"` | Entry type (cannot use `"interjection"` here) | | `priority` | string | No | `"normal"` (default) or `"urgent"` | Entry priority | | `target_id` | string | No | 19-digit numeric profile ID | Target user profile ID | | `properties` | object | No | -- | Metadata properties | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/worklogs/task/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/append/" \ -H "Authorization: Bearer {jwt_token}" \ -d "content=Completed%20initial%20review&entry_type=info" ``` **Response:** Returns the created entry under the `entry` key. --- ### Create Interjection #### `POST /current/worklogs/{entity_type}/{entity_id}/interjection/` Create an interjection -- an urgent entry requiring acknowledgement. Automatically sets `entry_type: "interjection"` and `priority: "urgent"`. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `content` | string | Yes | 1-65535 characters | Interjection text | | `target_id` | string | No | 19-digit numeric profile ID | Target user profile ID | | `properties` | object | No | -- | Metadata properties | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/worklogs/task/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/interjection/" \ -H "Authorization: Bearer {jwt_token}" \ -d "content=STOP%3A%20The%20uploaded%20contract%20has%20incorrect%20terms&target_id=9876543210987654321" ``` **Response:** Returns the created interjection entry under the `entry` key. --- ### List Unacknowledged Interjections #### `GET /current/worklogs/{entity_type}/{entity_id}/interjections/` List unacknowledged interjections for an entity. **Auth:** Required (JWT). Rate limited. --- ### Get Entry Details #### `GET /current/worklogs/{entry_id}/details/` Get full details of a single worklog entry. **Auth:** Required (JWT). Rate limited. **Response:** Returns the entry under the `entry` key. --- ### Acknowledge Interjection #### `POST /current/worklogs/{entry_id}/acknowledge/` Acknowledge an interjection. Only interjection-type entries can be acknowledged. **Auth:** Required (JWT). Rate limited. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/worklogs/w1x2y3z4a5b6c7d8e9f0g1h2i3j4k5/acknowledge/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Returns the updated entry with `acknowledged: true`, `acknowledgable: false`, `acknowledged_at`, and `updated` set. **Notes:** - Acknowledging a standard (non-interjection) entry returns an error. - Acknowledging an already-acknowledged interjection returns an error. - Worklog entries are append-only and immutable. Once created, they cannot be edited or deleted. --- ## Approvals Formal approval requests attached to entities (tasks, nodes, worklog entries, or shares). A designated approver or any admin can approve or reject. ### Approval Status | Value | Description | |-------|-------------| | `"pending"` | Awaiting resolution | | `"approved"` | Approved | | `"rejected"` | Rejected | ### Approval Entity Types | Value | Description | |-------|-------------| | `"task"` | Approval on a task | | `"node"` | Approval on a file/folder | | `"worklog_entry"` | Approval on a worklog entry | | `"share"` | Approval on a share profile (shared folders and portals only; workspaces, orgs, and users are rejected) | ### Approval Object | Field | Type | Description | |-------|------|-------------| | `id` | string | 30-character alphanumeric ID | | `entity_type` | string | `"task"`, `"node"`, `"worklog_entry"`, `"share"` | | `entity_id` | string | ID of the entity being approved | | `profile_id` | string | Workspace or share profile ID (19-digit numeric) | | `requested_by` | string | User profile ID of the requester | | `description` | string | What is being approved | | `status` | string | `"pending"`, `"approved"`, `"rejected"` | | `approver_id` | string\|null | Designated approver's profile ID, or null (any admin) | | `resolved_by` | string\|null | Profile ID of the resolver, or null | | `resolved_at` | string\|null | Resolution timestamp (`YYYY-MM-DD HH:MM:SS`), or null | | `comment` | string\|null | Resolution comment, or null | | `deadline` | string\|null | Deadline (`YYYY-MM-DD HH:MM:SS`), or null | | `version_id` | string\|null | Version ID of the node when the approval was created (node-type only), or null | | `version_match` | boolean\|null | Whether the approval's version matches the node's current version: `true` = current, `false` = stale (file updated since approval created), `null` = non-node entity or legacy approval. Computed at read time | | `node_id` | string\|null | Associated artifact node ID, or null | | `properties` | object\|null | Metadata properties (JSON) | | `created` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | --- ### List Approvals #### `GET /current/workspace/{workspace_id}/approvals/` #### `GET /current/share/{share_id}/approvals/` List approval requests in a workspace or share. > **Backwards compatibility:** The previous routes (`/current/approvals/workspace/{workspace_id}/` and `/current/approvals/share/{share_id}/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 200 | Number of approvals to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `status` | string | No | -- | `"pending"`, `"approved"`, `"rejected"` | Filter by status | | `format` | string | No | -- | `"md"` | Markdown output | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/approvals/?status=pending" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "approvals": [ { "id": "r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5", "entity_type": "task", "entity_id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "profile_id": "1234567890123456789", "requested_by": "9876543210987654321", "description": "Please review and approve the deployment plan.", "status": "pending", "approver_id": "5678901234567890123", "resolved_by": null, "resolved_at": null, "comment": null, "deadline": "2025-06-15T23:59:59+00:00", "version_id": null, "version_match": null, "node_id": null, "properties": null, "created": "2025-06-10T09:00:00+00:00", "updated": "2025-06-10T09:00:00+00:00" } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }, "current_api_version": "1.0" } ``` --- ### Create Approval #### `POST /current/approvals/{entity_type}/{entity_id}/create/` Create an approval request on an entity. **Auth:** Required (JWT). Rate limited. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{entity_type}` | string | Yes | `"task"`, `"node"`, `"worklog_entry"`, `"share"` | | `{entity_id}` | string | Yes | Entity ID (for `"share"`, must be a valid share profile ID) | **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `description` | string | Yes | 1-65535 characters | What is being approved | | `profile_id` | string | Yes | 19-digit numeric | Workspace or share profile ID | | `approver_id` | string | No | 19-digit numeric profile ID | Designated approver; omit for any admin | | `deadline` | string | No | `YYYY-MM-DD HH:MM:SS` | Informational deadline | | `node_id` | string | No | Alphanumeric opaque ID | Associated artifact node ID | | `properties` | object | No | -- | Metadata properties | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/approvals/task/x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5/create/" \ -H "Authorization: Bearer {jwt_token}" \ -d "description=Please%20review%20the%20deployment%20plan&profile_id=1234567890123456789&approver_id=5678901234567890123&deadline=2025-06-15T23:59:59%2B00:00" ``` **Response:** Returns the created approval under the `approval` key. **Notes:** - `approver_id` is a single user (not an array). If omitted, any admin can resolve. - `approver_id` can refer to a pending member (an invited user who has not yet signed up). The approval is created with the pending member as the designated approver. When the user claims their account, the approval assignment is preserved. - There is no `title` field -- use `description` to describe what is being approved. - For `entity_type` `"share"`, the `entity_id` must be a valid share profile ID (19-digit numeric). Only shares are accepted -- workspaces, orgs, and users are rejected. Only share members and admins can create (guests are blocked). - For share approvals, the `entity_id` must match the `profile_id` -- they must refer to the same share. - The share must be active (not archived or disabled). - For `entity_type` `"node"`, the current file version is automatically captured as `version_id`. --- ### Get Approval Details #### `GET /current/approvals/{approval_id}/details/` Get full details of an approval request. **Auth:** Required (JWT). Rate limited. **Response:** Returns the approval under the `approval` key. --- ### Resolve Approval #### `POST /current/approvals/{approval_id}/resolve/` Resolve an approval by approving or rejecting it. Only pending approvals can be resolved. **Auth:** Required (JWT). Designated approver or admin. Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `action` | string | Yes | `"approve"` or `"reject"` | Resolution action | | `comment` | string | No | Max 65535 characters | Resolution comment | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/approvals/r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5/resolve/" \ -H "Authorization: Bearer {jwt_token}" \ -d "action=approve&comment=Looks%20good%2C%20approved." ``` **Response:** Returns the resolved approval under the `approval` key with `status`, `resolved_by`, `resolved_at`, and `comment` updated. **Access Levels:** | Role | Access | |------|--------| | Designated Approver | Can resolve (if `approver_id` is set) | | Admin | Can resolve any approval | | Other Members | Cannot resolve | **Notes:** - The field is `action` (not `resolution`). Values: `"approve"` or `"reject"`. - Only approvals with `"pending"` status can be resolved. - Stale approvals (`version_match = false`) cannot be resolved -- returns error "This approval is stale". Create a new approval for the updated file version. - The resolution comment is stored in `comment` (not `resolution_comment`). --- ### Update Approval #### `POST /current/approvals/{approval_id}/update/` Update a pending approval. Only approvals with `"pending"` status can be updated -- resolved approvals are locked. **Auth:** Required (JWT). Members and admins only (guests blocked). Rate limited. **Request Body (all fields optional, at least one required):** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `description` | string | No | 1-65535 characters | Updated description | | `approver_id` | string | No | 19-digit numeric profile ID | Updated designated approver | | `deadline` | string | No | `YYYY-MM-DD HH:MM:SS` | Updated deadline | | `node_id` | string | No | Alphanumeric opaque ID | Updated associated node ID | | `properties` | object | No | -- | Updated metadata properties | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/approvals/r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5/update/" \ -H "Authorization: Bearer {jwt_token}" \ -d "description=Updated%20review%20request&deadline=2025-07-01T23:59:59%2B00:00" ``` **Response:** Returns the updated approval under the `approval` key with `version_match` computed. **Notes:** - Only pending approvals can be updated. Resolved approvals return an error. - The `status`, `entity_type`, `entity_id`, and `profile_id` fields cannot be changed. - At least one updatable field must be provided. --- ### Delete Approval #### `POST /current/approvals/{approval_id}/delete/` Delete an approval. Both pending and resolved approvals can be deleted. **Auth:** Required (JWT). Members and admins only (guests blocked). Rate limited. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/approvals/r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5/delete/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": true, "deleted": true, "approval_id": "r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5" } ``` **Notes:** - Deletion is permanent and irreversible. - Both pending and resolved approvals can be deleted. --- ### Bulk Approval Operations #### `POST /current/approvals/bulk/{action}/` Perform bulk operations on approvals within a share. Bulk create and bulk delete require member or admin permissions (guests blocked). Bulk approve/reject is accessible to guests but only processes approvals assigned to them (where approver_id matches their user ID). **Auth:** Required (JWT). Rate limited. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{action}` | string | Yes | `"create"`, `"approve"`, `"reject"`, or `"delete"` | #### Bulk Create (`action = "create"`) Creates node approvals for every file and note in a share. Auto-captures `version_id` for each node. Limited to shares with 1-50 file/note nodes; rejected if >50 or 0 nodes found. Best-effort: creates as many as possible. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `profile_id` | string | Yes | Share profile ID (19-digit numeric) | | `description` | string | Yes | Description for all approvals (1-65535 characters) | | `approver_id` | string | No | Designated approver's profile ID | | `deadline` | string | No | Deadline datetime (`YYYY-MM-DD HH:MM:SS`) | **Response:** ```json { "result": "yes", "response": { "action": "create", "profile_id": "12345678901234567890", "created_count": 5, "failed_count": 0, "total_nodes": 5, "approvals": ["a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5"] }, "current_api_version": "1.0" } ``` #### Bulk Approve (`action = "approve"`) Approves all pending approvals in a share. Atomic resolution (race-safe). Skips already-resolved and stale approvals (where the file has been updated since the approval was created). Stale approvals increment `skipped_count`. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `profile_id` | string | Yes | Share profile ID (19-digit numeric) | | `comment` | string | No | Resolution comment (max 65535 characters) | **Response:** ```json { "result": "yes", "response": { "action": "approve", "profile_id": "12345678901234567890", "approved_count": 4, "failed_count": 0, "skipped_count": 1 }, "current_api_version": "1.0" } ``` #### Bulk Reject (`action = "reject"`) Same as bulk approve but rejects. Response uses `rejected_count` instead of `approved_count`. #### Bulk Delete (`action = "delete"`) Deletes all approvals in a share (both pending and resolved), up to 500. Best effort. Members and admins only -- guests blocked. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `profile_id` | string | Yes | Share profile ID (19-digit numeric) | **Response:** ```json { "result": "yes", "response": { "action": "delete", "deleted_count": 12, "failed_count": 0, "total": 12 }, "current_api_version": "1.0" } ``` --- ### User Approvals List #### `GET /current/user/approvals/list/{filter}/` List approvals for the authenticated user across all profiles. No specific workspace or share membership required. **Auth:** Required (JWT). Rate limited. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `{filter}` | string | Yes | `"pending"`, `"created"`, or `"resolved"` | **Filter Behavior:** | Filter | Description | |--------|-------------| | `"pending"` | Approvals where the user is the designated approver and status is pending | | `"created"` | Approvals the user requested (any status). Supports optional `status` query param | | `"resolved"` | Approvals where the user was the approver that have been approved or rejected | **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 100 | Number of approvals to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `status` | string | No | -- | `"pending"`, `"approved"`, `"rejected"` | Filter by status (only for `"created"` filter) | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/user/approvals/list/pending/?limit=25" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": true, "approvals": [ { "id": "r1s2t3u4v5w6x7y8z9a0b1c2d3e4f5", "entity_type": "task", "entity_id": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5", "profile_id": "1234567890123456789", "requested_by": "9876543210987654321", "description": "Please review the deployment plan.", "status": "pending", "approver_id": "5678901234567890123", "resolved_by": null, "resolved_at": null, "comment": null, "deadline": "2025-06-15T23:59:59+00:00", "version_id": null, "version_match": null, "node_id": null, "properties": null, "created": "2025-06-10T09:00:00+00:00", "updated": "2025-06-10T09:00:00+00:00" } ], "pagination": { "limit": 25, "offset": 0, "total": 15, "filter": "pending" } } ``` --- ## Todos Lightweight checklists scoped to workspaces or shares. Each todo has a title, done state, optional assignee, and sort order. ### Todo Object | Field | Type | Description | |-------|------|-------------| | `id` | string | 30-character alphanumeric ID | | `profile_id` | string | Workspace or share profile ID (19-digit numeric) | | `created_by` | string | User profile ID of the todo creator | | `title` | string | Todo title text | | `done` | int | `0` = not done, `1` = done | | `assignee_id` | string\|null | Assigned user's profile ID, or null | | `sort_order` | int | Display sort order | | `properties` | object\|null | Metadata properties (JSON) | | `created` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `updated` | string | Timestamp (`YYYY-MM-DD HH:MM:SS`) | | `deleted` | string\|null | Deletion timestamp (`YYYY-MM-DD HH:MM:SS`), or null | Todos do **not** have a `description` field. The `done` field is an integer (0 or 1), not a boolean. --- ### List Todos #### `GET /current/workspace/{workspace_id}/todos/` #### `GET /current/share/{share_id}/todos/` List all todos in a workspace or share. > **Backwards compatibility:** The previous routes (`/current/todos/workspace/{workspace_id}/` and `/current/todos/share/{share_id}/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Query Parameters:** | Parameter | Type | Required | Default | Constraints | Description | |-----------|------|----------|---------|-------------|-------------| | `limit` | int | No | `50` | Max 200 | Number of todos to return | | `offset` | int | No | `0` | Min 0 | Number to skip | | `format` | string | No | -- | `"md"` | Markdown output | **Request Example:** ```bash curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/todos/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** ```json { "result": "yes", "response": { "todos": [ { "id": "t1u2v3w4x5y6z7a8b9c0d1e2f3g4h5", "profile_id": "1234567890123456789", "title": "Review uploaded contracts", "done": 0, "assignee_id": null, "sort_order": 0, "properties": null, "created": "2025-06-10T09:00:00+00:00", "updated": "2025-06-10T09:00:00+00:00", "deleted": null }, { "id": "u1v2w3x4y5z6a7b8c9d0e1f2g3h4i5", "profile_id": "1234567890123456789", "title": "Send report to stakeholders", "done": 1, "assignee_id": "9876543210987654321", "sort_order": 1, "properties": null, "created": "2025-06-09T14:00:00+00:00", "updated": "2025-06-10T10:30:00+00:00", "deleted": null } ], "pagination": { "limit": 50, "offset": 0, "total": 2 } }, "current_api_version": "1.0" } ``` --- ### Create Todo #### `POST /current/workspace/{workspace_id}/todos/create/` #### `POST /current/share/{share_id}/todos/create/` Create a new todo. > **Backwards compatibility:** The previous routes (`/current/todos/workspace/{workspace_id}/create/` and `/current/todos/share/{share_id}/create/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `title` | string | Yes | 1-512 characters | Todo title | | `assignee_id` | string | No | 19-digit numeric profile ID | User to assign | | `sort_order` | int | No | Default 0 | Display sort order | | `properties` | object | No | -- | Metadata properties | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/todos/create/" \ -H "Authorization: Bearer {jwt_token}" \ -d "title=Review%20uploaded%20contracts" ``` **Response:** Returns the created todo under the `todo` key. Todos are created with `done: 0`. **Notes:** - The `assignee_id` can refer to a pending member (an invited user who has not yet signed up). Pending members have a valid user ID and can be assigned to todos before they create their account. When the user claims their account, the assignment is preserved. --- ### Get Todo Details #### `GET /current/todos/{todo_id}/details/` Get full details of a single todo. **Auth:** Required (JWT). Rate limited. **Response:** Returns the todo under the `todo` key. --- ### Update Todo #### `POST /current/todos/{todo_id}/details/update/` Update a todo. Only provided fields are changed. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `title` | string | No | 1-512 characters | Updated title | | `done` | int | No | 0 or 1 | Updated done state | | `assignee_id` | string\|null | No | 19-digit numeric; `null` to unassign | Updated assignee | | `sort_order` | int | No | -- | Updated sort order | | `properties` | object | No | -- | Updated properties | **Important:** The endpoint path includes `/details/` (i.e., `/todos/{todo_id}/details/update/`). --- ### Delete Todo #### `POST /current/todos/{todo_id}/details/delete/` Soft-delete a todo. **Auth:** Required (JWT). Rate limited. --- ### Toggle Todo #### `POST /current/todos/{todo_id}/details/toggle/` Toggle a todo's done state. `0` becomes `1`, `1` becomes `0`. **Auth:** Required (JWT). Rate limited. **Request Example:** ```bash curl -X POST "https://api.fast.io/current/todos/t1u2v3w4x5y6z7a8b9c0d1e2f3g4h5/details/toggle/" \ -H "Authorization: Bearer {jwt_token}" ``` **Response:** Returns the updated todo under the `todo` key with the toggled `done` value. --- ### Bulk Toggle Todos #### `POST /current/workspace/{workspace_id}/todos/bulk-toggle/` #### `POST /current/share/{share_id}/todos/bulk-toggle/` Bulk toggle the done state of multiple todos. > **Backwards compatibility:** The previous routes (`/current/todos/workspace/{workspace_id}/bulk-toggle/` and `/current/todos/share/{share_id}/bulk-toggle/`) continue to work via automatic redirect. **Auth:** Required (JWT). Rate limited. **Request Body:** | Field | Type | Required | Constraints | Description | |-------|------|----------|-------------|-------------| | `todo_ids` | array\ | Yes | Max 100 items | Todo IDs to toggle | | `done` | bool | Yes | `true` or `false` | Target state: `true` = mark all done, `false` = mark all not done | **Request Example:** ```bash curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/todos/bulk-toggle/" \ -H "Authorization: Bearer {jwt_token}" \ -H "Content-Type: application/json" \ -d '{"todo_ids": ["t1u2v3...", "u1v2w3..."], "done": true}' ``` **Response:** ```json { "result": "yes", "response": { "success": true, "updated_count": 2, "failed_count": 0, "total_count": 2, "results": [ {"todo_id": "t1u2v3...", "success": true, "error": null}, {"todo_id": "u1v2w3...", "success": true, "error": null} ] }, "current_api_version": "1.0" } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | `response.success` | bool | `true` if all todos updated successfully | | `response.updated_count` | int | Number successfully updated | | `response.failed_count` | int | Number that failed | | `response.total_count` | int | Total number of todo IDs provided | | `response.results` | array | Per-todo results with `todo_id`, `success`, and `error` | --- ## Profile-Scoped Filtered Lists & Summaries All four workflow types (approvals, tasks, todos, worklogs) support profile-scoped filtered list and summary endpoints. These replace the need for global cross-profile queries in most use cases. - **Workspace context** = personal view (filtered to the current user) - **Share context** = group view for owners (all items), scoped view for guests (approvals assigned to them only) - **Pagination:** All endpoints support `?limit=` (default 50, max 100) and `?offset=` (default 0) ### Share Guest Access On shares, only approvals are accessible to guests. Tasks, todos, and worklogs require member or admin access. | Operation | Owner/Admin/Member | Share Guest | Public Guest | |-----------|-------------------|-------------|-------------| | View approvals | All approvals | Assigned only | No | | Create approvals | Yes | No | No | | Resolve approvals | Any | Assigned only | No | | View/create/resolve tasks | Yes | No | No | | View/create/toggle todos | Yes | No | No | | View/create/acknowledge worklogs | Yes | No | No | --- ### Approval Filtered Lists #### `GET /current/workspace/{workspace_id}/approvals/list/{filter}/` Personal view. Filters: `pending` (assigned to me, status=pending), `created` (I created, optional `?status=`), `assigned` (assigned to me, optional `?status=`), `resolved` (I resolved). #### `GET /current/share/{share_id}/approvals/list/{filter}/` For owners/members: group view (all approvals). For guests: scoped to approvals where approver_id matches their user ID. The `created` filter returns empty for guests (they cannot create). Guests can access. #### `GET /current/workspace/{workspace_id}/approvals/list/summary/` Returns `created_by_me` and `assigned_to_me` counts with `pending`, `approved`, `rejected`, `total` breakdowns. #### `GET /current/share/{share_id}/approvals/list/summary/` Owners/members: returns `all` + `assigned_to_me` counts. Guests: returns `assigned_to_me` counts only. All breakdowns include `pending`, `approved`, `rejected`, `total`. Guests can access. --- ### Task Filtered Lists #### `GET /current/workspace/{workspace_id}/tasks/list/{filter}/` #### `GET /current/share/{share_id}/tasks/list/{filter}/` Filters: `assigned` (assigned to user, optional `?status=`), `created` (created by user, optional `?status=`), `status` (requires `?status=` query param). Share endpoint shows group view. Members and admins only -- guests are blocked. #### `GET /current/workspace/{workspace_id}/tasks/summary/` #### `GET /current/share/{share_id}/tasks/summary/` Returns total count, counts by status, tasks assigned to current user, and tasks created by current user. Members and admins only on share. --- ### Todo Filtered Lists #### `GET /current/workspace/{workspace_id}/todos/list/{filter}/` #### `GET /current/share/{share_id}/todos/list/{filter}/` Filters: `assigned` (assigned to user), `created` (created by user), `done` (completed), `pending` (incomplete). Share endpoint shows group view. Members and admins only -- guests are blocked. #### `GET /current/workspace/{workspace_id}/todos/summary/` #### `GET /current/share/{share_id}/todos/summary/` Returns done/pending counts and user-specific breakdowns. Members and admins only on share. --- ### Worklog Filtered Lists #### `GET /current/workspace/{workspace_id}/worklogs/` #### `GET /current/share/{share_id}/worklogs/` Base listing of all worklog entries in a workspace or share. Members and admins only on share -- guests are blocked. #### `GET /current/workspace/{workspace_id}/worklogs/list/{filter}/` #### `GET /current/share/{share_id}/worklogs/list/{filter}/` Filters: `authored` (entries by current user, optional `?entry_type=`), `interjections` (unacknowledged interjections targeted at current user). Share endpoint shows group view. Members and admins only -- guests are blocked. #### `GET /current/workspace/{workspace_id}/worklogs/summary/` #### `GET /current/share/{share_id}/worklogs/summary/` Returns entry type counts and user-specific breakdowns. Members and admins only on share. --- ## Workflow Events Workflow primitives emit events on all state changes. Filter with `GET /current/events/search/?workspace_id={id}&category=workflow`. **Task events:** `task_list_created`, `task_list_updated`, `task_list_deleted`, `task_created`, `task_updated`, `task_deleted`, `task_status_changed`, `task_assigned`, `task_moved`, `task_completed`, `tasks_reordered`, `task_lists_reordered` **Worklog events:** `worklog_entry_created`, `worklog_interjection_created`, `worklog_interjection_acknowledged` **Approval events:** `approval_requested`, `approval_approved`, `approval_rejected` **Todo events:** `todo_created`, `todo_toggled`, `todo_deleted`, `todo_updated` All workflow events use event category `workflow` and subcategory `workflow`.