Workspace Management Create, configure, and manage workspaces within organizations. Control members, assets, shares, and AI intelligence settings.
Workspaces are shared project spaces within an organization. They support file versioning, search, AI chat, and member management. 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 | Endpoint | 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 | Endpoint | 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 |
| GET | /current/workspace/{workspace_id}/assets/{asset_name}/read/ | Get asset metadata headers (HEAD) |
Members
| Method | Endpoint | 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 |
| POST | /current/workspace/{workspace_id}/member/{member_id}/transfer_ownership/ | Transfer ownership |
Invitations
| Method | Endpoint | 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 | Endpoint | 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 |
File Shares (durable single-file links)
| Method | Endpoint | Description |
|---|---|---|
| POST | /current/workspace/{workspace_id}/create/fileshare/ | Create a File Share bound to a file node |
| GET | /current/workspace/{workspace_id}/list/fileshares/ | List the workspace's File Shares |
| POST / PATCH | /current/fileshare/{fileshare_id}/update/ | Update title / access tier / password |
| DELETE | /current/fileshare/{fileshare_id}/delete/ | Delete a File Share |
| GET / POST / DELETE | /current/fileshare/{fileshare_id}/grants/ | List, grant, or revoke per-user capabilities |
Cloud Import
| Method | Endpoint | Description |
|---|---|---|
| POST | /current/workspace/{workspace_id}/cloud-import/enable/ | Enable cloud import |
| POST | /current/workspace/{workspace_id}/cloud-import/disable/ | Disable cloud import |
| GET | /current/imports/workspace/{workspace_id}/identities/ | List provider identities |
| POST | /current/imports/workspace/{workspace_id}/identities/provision/ | Provision a provider identity |
| POST | /current/imports/oauth/{provider}/complete/ | Finish a browser OAuth connect (Dropbox, OneDrive) |
| GET | /current/imports/workspace/{workspace_id}/identities/{identity_id}/ | Get identity details |
| POST | /current/imports/workspace/{workspace_id}/identities/{identity_id}/revoke/ | Revoke a provider identity |
| GET | /current/imports/workspace/{workspace_id}/identities/{identity_id}/drives/ | List reachable drives (OneDrive for Business) |
| POST | /current/imports/workspace/{workspace_id}/identities/{identity_id}/drives/refresh/ | Refresh the drive catalog |
Discovery
| Method | Endpoint | 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 |
Compact Responses (output=)
Every endpoint that returns one or more workspace objects (details, list, discovery) accepts an optional output query parameter that selects the response shape. A single detail-level token may be combined with modifier tokens; specifying two detail levels (e.g. ?output=terse,standard) returns HTTP 406. When output= is omitted, responses are full and byte-for-byte unchanged.
| Level | Fields returned on each workspace (cumulative) |
|---|---|
terse | id, name, folder_name, org_domain, user_status |
standard | terse + description, workspace_level, closed, archived, locked (admin-only), storage, created, updated, logo, accent_color, intelligence, capabilities |
full | standard + cloud_import, comments, chat, search, assets, remaining branding, perm_* blocks, platform, suspended, owner_defined, parents |
Use terse for workspace switchers, autocomplete, and org-scoped navigation — it carries the identifier, display name, folder slug, parent org domain, and user_status so the two-column "Joined vs. Available" workspace list can render and the join button can enable/disable without a follow-up fetch. Use standard for workspace list views and the summary area of workspace detail pages — it adds lifecycle flags (including the locked admin-only chip), storage usage, timestamps, description, the workspace's visual identity (logo, accent_color), plus the intelligence feature toggles and the plan-gate capabilities bundle so list rows can differentiate "enabled but plan-locked" from "usable." Use full (or omit the parameter) for the workspace settings screen, branding editors, and any workflow that reads permission matrices or remaining feature blocks (comments, chat, search, assets). Unknown tokens are silently ignored. Add the markdown modifier (e.g. ?output=standard,markdown) to receive the response as GitHub-flavored Markdown (Content-Type: text/markdown; charset=UTF-8) instead of JSON — see the cross-cutting ?output= reference for the full contract.
Field Constraints
| Field | Constraint |
|---|---|
folder_name | 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) | 4–80 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) — files are auto-indexed for semantic search, summarization, and citation. Required forchat_with_filesAI chat type. Requires both thecontent_aiandai_agentplan features. Plans that lack either feature cannot setintelligence=true; the API rejects the create/update request with1605 (Invalid Input). - Disable (
intelligence=false) — files are stored/shared without RAG indexing. You can still attach files directly to achattype conversation for one-off analysis on plans that support chat. - Set at creation:
POST /current/org/{org_id}/create/workspace/withintelligence=true|false - Update later:
POST /current/workspace/{id}/update/withintelligence=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.
- Agent-account default: On plans that include
ai_agent, new workspaces created by agent accounts defaultintelligence=trueif the parameter is omitted. On plans withoutai_agent(including the legacy agent plan), the default isfalsebecause the indexing pipeline has no consumer. See the AI reference for the full plan matrix.
Workspace CRUD
Create Workspace
/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 on plans with ai_agent) | "true" or "false" | Enable AI indexing. Defaults to "true" for agent accounts only on plans that include the ai_agent feature; otherwise the default is "false". Setting "true" requires both content_ai and ai_agent. |
| description | string | 10–1000 chars | Workspace description | |
| accent_color | string (JSON) | Valid JSON | Accent color styling | |
| background_color1 | string (JSON) | Valid JSON | Background color 1 styling | |
| background_color2 | string (JSON) | Valid JSON | Background color 2 styling |
Example
curl -X POST "https://api.fast.io/current/org/1000000000000000001/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)
{
"result": true,
"workspace": {
"id": "1234567890123456789",
"folder_name": "engineering"
}
}
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) | 412 | Workspace creation is not available on your current plan. | Feature disabled on billing plan |
1685 (Feature Limit) | 412 | 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) | 406 | An invalid workspace folder name was supplied. | Invalid folder_name format |
1605 (Invalid Input) | 406 | 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
/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 |
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"workspace": {
"id": "1234567890123456789",
"name": "Engineering Team",
"folder_name": "engineering",
"description": "Main engineering workspace",
"accent_color": "#0066CC",
"logo": "https://assets.fast.io/1234567890123456789/logo.png",
"closed": false,
"archived": false,
"perm_join": "Member or above",
"perm_member_manage": "Admin or above",
"created": "2023-01-15 10:30:00 UTC",
"updated": "2024-01-20 14:45:00 UTC",
"user_status": "joined",
"org_domain": "acme-corp",
"nl_summaries_daily_cap": 100
}
}
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 |
| response.workspace.nl_summaries_daily_cap | integer | Per-day ceiling on AI enrichment operations (default 100) |
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
/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. Setting to "true" requires both content_ai and ai_agent plan features. 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 |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/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)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | User is not admin or owner |
1605 (Invalid Input) | 406 | 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 |
1685 (Feature Limit) | 412 | Intelligence setting can only be changed twice per minute and five times per hour... | Intelligence toggle rate-limited by the per-window throttle |
1605 (Invalid Input) | 406 | Intelligence requires a plan with agentic AI support. | Plan missing content_ai or ai_agent (cannot set intelligence=true) |
1605 (Invalid Input) | 406 | An invalid configuration was supplied... | Metadata validation failure |
1663 (Update Failed) | 500 | There was an internal error processing your update request. | Internal failure |
If no fields have changed, returns
200 OKwithout making changes. JSON fields are decoded server-side; send valid JSON strings.
Delete Workspace
/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. |
Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/delete/?confirm=engineering" \
-H "Authorization: Bearer {jwt_token}"
Response (202 Accepted)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | User is not the workspace owner |
130670 | 406 | The confirm field is required. Pass the workspace's folder name or numeric id as the confirm query parameter. | confirm query parameter was not provided |
10563 | 406 | The confirm field provided does not match the workspace's folder name or id. | Confirmation does not match folder name or ID |
1663 (Update Failed) | 500 | There was an internal error processing your request. | Internal failure |
Archive Workspace
/current/workspace/{workspace_id}/archive/
Archives a workspace. Archived workspaces are hidden from default listings.
Auth: JWT required. Admin or Owner required. 2FA required.
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/archive/" \
-H "Authorization: Bearer {jwt_token}"
Response (202 Accepted)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | Not admin or owner |
1663 (Update Failed) | 500 | The workspace is already archived. | Already archived |
Unarchive Workspace
/current/workspace/{workspace_id}/unarchive/
Restores an archived workspace to active status.
Auth: JWT required. Admin or Owner required. 2FA required.
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/unarchive/" \
-H "Authorization: Bearer {jwt_token}"
Response (202 Accepted)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | Not admin or owner |
1663 (Update Failed) | 500 | The workspace is not archived. It cannot be unarchived. | Not currently archived |
Workspace Assets
List Available Asset Types
/current/workspace/assets/
Returns available workspace asset metadata types (e.g., logo).
Auth: JWT required.
Example
curl -X GET "https://api.fast.io/current/workspace/assets/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"assets": [
{
"name": "logo",
"type": "image",
"max_size": 5242880,
"accepted_formats": ["image/png", "image/jpeg", "image/svg+xml"]
}
]
}
List Workspace Assets
/current/workspace/{workspace_id}/assets/
Returns assets currently set on the workspace.
Auth: JWT required. Owner only. 2FA required.
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/assets/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"assets": {
"logo": {
"url": "https://assets.fast.io/1234567890123456789/logo.png",
"size": 102400,
"content_type": "image/png"
}
}
}
Upload/Set Workspace Asset
/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) | Optional metadata for the asset |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/assets/logo/" \
-H "Authorization: Bearer {jwt_token}" \
-F "file=@/path/to/logo.png" \
-F "metadata={}"
Response (200 OK)
{
"result": true,
"asset": {
"name": "logo",
"url": "https://assets.fast.io/1234567890123456789/logo.png"
}
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1691 (File Missing) | 412 | Asset upload missing | No file in request |
1605 (Invalid Input) | 406 | metadata invalid | Invalid metadata format |
Delete Workspace Asset
/current/workspace/{workspace_id}/assets/{asset_name}/
Delete a specific asset from a workspace.
Auth: JWT required. Admin or Owner required.
Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/assets/logo/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Read Asset Binary
/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.
Example
# Download asset
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/assets/logo/read/" \
-H "Authorization: Bearer {jwt_token}" \
--output logo.png
# Get metadata headers only
curl -I "https://api.fast.io/current/workspace/1234567890123456789/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
/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 | "admin", "member", or "guest". Cannot be "owner". | |
| notifications | string | Notification preference | |
| expires | string | Membership expiration (YYYY-MM-DD HH:MM:SS UTC) | |
| force_notification | boolean | Force notification email to existing user |
Request Parameters (inviting by email)
| Name | Type | Required | Description |
|---|---|---|---|
| permissions | string | "admin", "member", or "guest". Cannot be "owner". | |
| message | string | Custom message in invitation email | |
| invitation_expires | string | Invitation expiration (YYYY-MM-DD HH:MM:SS UTC) |
Examples
# Add existing user by ID
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/9876543210987654321/" \
-H "Authorization: Bearer {jwt_token}" \
-d "permissions=member"
# Invite by email
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/newuser@example.com/" \
-H "Authorization: Bearer {jwt_token}" \
-d "permissions=member" \
-d "message=Welcome to the project!"
Response — Direct Add (200 OK)
{
"result": true
}
Response — Invitation Sent (200 OK)
{
"result": true,
"invitation": {
"id": "1234567890123456789",
"inviter": "John Doe",
"invitee_email": "newuser@example.com",
"entity_type": "workspace",
"state": "pending",
"created": "2025-01-15 10:30:00 UTC",
"expires": "2025-02-15 10:30:00 UTC"
}
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1692 (Cannot Add As Owner) | 500 | Adding a member as an owner is not allowed | Attempted owner-level permission |
1656 (Limit Exceeded) | 413 | Varies | Workspace or org member limit reached |
1680 (Access Denied) | 401 | Insufficient org permissions to invite to this workspace. | Lacks org-level permission |
1680 (Access Denied) | 401 | Insufficient permissions | Below workspace perm_member_manage level |
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
/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.
Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/members/9876543210987654321/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | Cannot remove the owner | Attempted to remove workspace owner |
1680 (Access Denied) | 401 | Insufficient permissions | Below required permission level |
Removing a member cascades removal into all shares within the workspace.
List Workspace Members
/current/workspace/{workspace_id}/members/list/
Lists all members with their permissions, notification preferences, and membership metadata.
Auth: JWT required. Any workspace member.
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/members/list/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"users": [
{
"id": "1234567890123456789",
"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": "9876543210987654321",
"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 UTC"
},
{
"id": "5566778899001122334",
"account_type": "human",
"email_address": "invited@example.com",
"first_name": "invited@example.com",
"last_name": "",
"permissions": "member",
"status": "pending",
"invite": {
"id": "5566778899001122334",
"created": "2025-01-15 10:30:00 UTC",
"expires": "2025-02-15 10:30:00 UTC"
},
"notify": "Notify me in app",
"expires": null
}
]
}
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)
/current/workspace/{workspace_id}/member/
Removes the authenticated user from the workspace. Owners cannot leave; they must transfer ownership first.
Auth: JWT required.
Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/member/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | You cannot leave a workspace you are the owner of, transfer ownership or close workspace. | User is the owner |
1605 (Invalid Input) | 406 | You cannot leave an workspace you are not a member of. | Not a member |
Get Member Details
/current/workspace/{workspace_id}/member/{member_id}/details/
Returns membership details for a specific user.
Auth: JWT required. Any workspace member.
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/member/9876543210987654321/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"member": {
"id": "9876543210987654321",
"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
}
}
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) | 406 | The membership you specified does not exist. | User is not a member |
Update a Member
/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 UTC) |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/member/9876543210987654321/update/" \
-H "Authorization: Bearer {jwt_token}" \
-d "permissions=admin"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | The membership you specified does not exist. | Target is not a member |
1680 (Access Denied) | 401 | Insufficient permissions | Below required permission level |
Users cannot escalate permissions beyond their own level.
Transfer Workspace Ownership
/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.
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/member/9876543210987654321/transfer_ownership/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | You cannot transfer ownership to yourself. | Target is self |
1605 (Invalid Input) | 406 | The membership you specified does not exist. | Target is not a workspace member |
1605 (Invalid Input) | 406 | Member is already an owner. | Target is already owner |
1605 (Invalid Input) | 406 | 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) | 401 | Insufficient permissions | Not the workspace owner |
Join Workspace
/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 | Notification preference |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/join/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | You do not have the appropriate permissions to join this workspace. | User's org role does not meet perm_join |
1656 (Limit Exceeded) | 413 | Varies | Workspace member limit reached |
Self-joining members are assigned the
memberrole. Only notification preferences are accepted. Self-joined memberships do not expire.
Join Workspace via Invitation
/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 | "accept" (default) or "decline" |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/join/abc123def456/accept/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1654 (Internal Error) | 500 | Failed to get invitation. | Invalid or expired invitation key |
1680 (Access Denied) | 401 | The inviter no longer has appropriate permissions... | Inviter lost management permissions |
1656 (Limit Exceeded) | 413 | Varies | Member limit reached |
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 Fastio account, they appear as a pending member in the member list.
How pending members appear in responses:
- The
statusfield is"pending"(vs"active"for registered users). - An
inviteobject is included with basic invitation details (id,created,expires). email_addressshows the invited email address.first_nameis set to the invited email address;last_nameis empty.
Example Member List Entry (Pending)
{
"id": "5566778899001122334",
"account_type": "human",
"email_address": "newuser@example.com",
"first_name": "newuser@example.com",
"last_name": "",
"permissions": "member",
"status": "pending",
"invite": {
"id": "5566778899001122334",
"created": "2025-01-15 10:30:00 UTC",
"expires": "2025-02-15 10:30:00 UTC"
},
"notify": "Notify me in app",
"expires": null
}
Account claim: When the invited user signs up or accepts the invitation with an existing account, their status transitions from "pending" to "active".
Removal: To remove a pending member, delete their invitation using the invitation endpoints (see Workspace Invitations below). Deleting the invitation removes the pending member.
Notifications: Pending members do not receive in-app or email notifications until they claim their account.
Workspace Invitations
List Workspace Invitations
/current/workspace/{workspace_id}/members/invitations/list/
Returns all invitations for the workspace.
Auth: JWT required. Any workspace member.
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/members/invitations/list/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"invitations": [
{
"id": "1234567890123456789",
"inviter": "Alice Johnson",
"invitee_email": "newuser@example.com",
"invitee_uid": null,
"accepted_uid": null,
"entity_type": "workspace",
"workspace": {
"id": "9876543210987654321",
"name": "Project Alpha"
},
"state": "pending",
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC",
"expires": "2025-02-15 10:30:00 UTC"
}
]
}
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
/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" |
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/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) | 406 | An invalid invitation state was supplied. | Unrecognized state |
Update an Invitation
/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 UTC) |
Examples
# Revoke by ID
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/invitation/1111111111111111111/" \
-H "Authorization: Bearer {jwt_token}" \
-d "state=revoked"
# Update by email
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/members/invitation/user@example.com/" \
-H "Authorization: Bearer {jwt_token}" \
-d "permissions=admin"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | An invalid invitation ID or email was supplied. | Malformed identifier |
1605 (Invalid Input) | 406 | Invitation not found. | No matching invitation |
1605 (Invalid Input) | 406 | An invalid state was supplied. | Unrecognized state |
1679 (Update Failed) | 500 | Failed to update invitation. | Internal failure |
1680 (Access Denied) | 401 | Insufficient permissions | Below required permission level |
Delete an Invitation
/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.
Example
curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/members/invitation/1111111111111111111/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | An invalid invitation ID or email was supplied. | Malformed identifier |
1605 (Invalid Input) | 406 | Invitation not found. | No matching invitation |
1666 (Delete Failed) | 500 | Failed to delete invitation. | Internal failure |
1680 (Access Denied) | 401 | Insufficient permissions | Below required permission level |
Creating Shares from Workspaces
Create Share
/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 reference.
Required Parameters
| Name | Type | Constraints | Description |
|---|---|---|---|
| intelligence | string | "true" or "false" | Enable AI features for the share |
Optional Parameters
| Name | Type | Constraints | Description |
|---|---|---|---|
| share_type | string | "send", "receive", "exchange" | Type of share. Optional — falls back to the profile default when omitted. |
| access_options | string | See access options below | Access control setting. Optional — falls back to the profile default when omitted. |
| invite | string | "owners" or "guests" | Who can manage invitations. Optional — falls back to the profile default when omitted. |
| 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 | 1-255 characters | 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 | 4–80 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. |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/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)
{
"result": true,
"share": {
"id": "9876543210987654321",
"custom_name": "abc123opaque",
"storage_mode": "independent"
}
}
Response — Workspace Folder Share
{
"result": true,
"share": {
"id": "9876543210987654321",
"custom_name": "def456opaque",
"storage_mode": "workspace_folder",
"folder_node_id": "abc123def456"
}
}
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) | 412 | 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) | 406 | An invalid share custom name was supplied. | Invalid custom_name format |
1605 (Invalid Input) | 406 | Workspace folder shares cannot have an expiration date. | expires set on workspace folder share |
1605 (Invalid Input) | 406 | Receive and Exchange shares cannot have "Anyone" access option. | Invalid access/type combination |
1605 (Invalid Input) | 406 | 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
/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 |
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/list/shares/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"shares": [
{
"id": "9876543210987654321",
"title": "Client Deliverables",
"share_type": "send",
"custom_name": "client-deliverables",
"archived": false,
"closed": false
}
]
}
Import Share into Workspace
/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 |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/import/share/9876543210987654321/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"share": {
"id": "9876543210987654321",
"parent_type": "workspace",
"parent_workspace": "1234567890123456789"
}
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | 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) | 406 | The share has multiple owners... | Remove other owners first |
1685 (Feature Limit) | 412 | The workspace has reached its share limit... | Plan limit exceeded |
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.
File Shares (Durable Single-File Links)
A File Share is a durable, link-shareable view of one workspace file — the successor to the deprecated QuickShare. It is durable by default (an optional expiry can be set on create/update) and has no per-link transfer cap (bandwidth is metered to the owning organization). A File Share is bound to a single file node at creation and the binding is immutable. The public read endpoints (details, download, preview, versions) are documented in the Shares and Storage references; the management endpoints below require the caller to be an authenticated member of the workspace.
Comment visibility is one-directional (workspace ⊇ File Share). Because a File Share is just a view of a workspace file, comments left by File Share recipients also surface to workspace members — read-only on the workspace file's comment thread and in workspace comment search. The reverse is never true: a File Share recipient sees only the comments made under that File Share and never the workspace's internal comments.
Create a File Share
/current/workspace/{workspace_id}/create/fileshare/
Create a File Share bound to a workspace file node. Auth required. Permission: Member of the workspace named in the path.
Request Body (form-encoded)
| Name | Type | Required | Description |
|---|---|---|---|
| node | string | Yes | OpaqueId of the file node to share (must be a file, not a folder or note) |
| title | string | No | Display title (max 255 chars) |
| access_option | string | No | anyone_with_link, any_registered, or named_people (default: named_people) |
| password | string | No | Optional link password (1-255 chars). Body-only — never accepted from the query string. |
| expires | integer | No | Optional expiry, RELATIVE: seconds from now (> 0). Mutually exclusive with expires_at. Omitted = durable (never expires). |
| expires_at | string | No | Optional expiry, ABSOLUTE: a future datetime; a value without a timezone is interpreted as UTC. Mutually exclusive with expires. An expired File Share stops serving immediately (404) and is reaped automatically by the hourly cleanup (grants cascaded, file_share_deleted fires); the bound file is never touched. |
curl Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/create/fileshare/" \
-H "Authorization: Bearer {jwt_token}" \
-d "node=f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf" \
-d "title=Quarterly Presentation" \
-d "access_option=anyone_with_link"
Response (200 OK)
{
"result": true,
"fileshare": {
"fileshare": "1234567890123456789",
"title": "Quarterly Presentation",
"access_option": "anyone_with_link",
"has_password": false,
"bound_node_id": "f3jm5zqzfxpxdr2dx8z5bvnb3rpjf",
"creator_uid": "9876543210987654321",
"created": "2026-04-27 16:37:29 UTC",
"updated": "2026-04-27 16:37:29 UTC",
"expires": null
}
}
Error Responses
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 406 | node missing/invalid, the node is not a file, or the password is unusable |
1680 (Access Denied) | 401 | The bound file is not publicly serveable (locked/DMCA/infected) |
List File Shares in Workspace
/current/workspace/{workspace_id}/list/fileshares/
Lists the workspace's File Shares (offset pagination). Scoped strictly to File Shares — never mixes with the Share list. Auth required. Permission: Member.
Each item additionally carries grant_count (the number of live named-people grants on the File Share — revoked / expired grants excluded) and grants_preview (the first few grants, in the same shape as the grants list endpoint, for rendering an avatar stack without a second call). Both are best-effort: if the grant read faults for an item, the two fields are omitted for that item (the rest of the list still returns). The full access list is the GET .../grants/ endpoint below.
Response (200 OK)
{
"result": true,
"fileshares": [
{
"fileshare": "1234567890123456789",
"title": "Quarterly Presentation",
"access_option": "anyone_with_link",
"has_password": false,
"bound_node_id": "f3jm5zqzfxpxdr2dx8z5bvnb3rpjf",
"creator_uid": "9876543210987654321",
"created": "2026-04-27 16:37:29 UTC",
"updated": "2026-04-27 16:37:29 UTC",
"expires": null,
"grant_count": 2,
"grants_preview": [
{
"user": "9876543210987654321",
"name": "Ada Lovelace",
"email": "ada@example.com",
"capability": "edit",
"state": "active",
"created": "2026-04-27 16:37:29 UTC",
"expires": null
},
{
"user": null,
"name": null,
"email": "pending.invitee@example.com",
"capability": "view",
"state": "pending",
"created": "2026-04-27 16:40:00 UTC",
"expires": "2026-05-27 16:40:00 UTC"
}
]
}
]
}
Update a File Share
/current/fileshare/{fileshare_id}/update/
/current/fileshare/{fileshare_id}/update/
Update a File Share's mutable settings. The bound file node is immutable here (a rebind is a new File Share); only title, access_option, password, and the expiry change. Auth required. Permission: Member of the File Share's parent workspace.
Request Body (form-encoded)
| Name | Type | Description |
|---|---|---|
| title | string | New title (max 255). Send empty / null to clear. |
| access_option | string | anyone_with_link, any_registered, or named_people |
| password | string | New link password (max 255). Body-only. Send empty to clear. |
| expires | integer | New expiry, RELATIVE: seconds from now (> 0). Mutually exclusive with expires_at. |
| expires_at | string | New expiry, ABSOLUTE: a future datetime (no timezone = UTC). Send null to clear the expiry (durable again). Mutually exclusive with expires. |
Returns the updated File Share in the same shape as create.
Delete a File Share
/current/fileshare/{fileshare_id}/delete/
Delete a File Share. Revokes the link and cascades its grants. Auth required. Permission: Member of the File Share's parent workspace. Response: {"result": true}.
List / Manage Per-User Grants
/current/fileshare/{fileshare_id}/grants/
/current/fileshare/{fileshare_id}/grants/
/current/fileshare/{fileshare_id}/grants/
List or manage the named-people access list. GET lists the File Share's live grants; POST grants or raises a user's capability; DELETE revokes (idempotent). A grant raises an individual user's capability regardless of tier; the named_people tier consults this list directly. Auth required. Permission: Member of the File Share's parent workspace (all three methods).
List (GET) Response (200 OK)
Returns the File Share's live named-people grants — revoked and expired grants are excluded. There are no pagination parameters (the access list is small; the first 1000 grants are returned).
{
"result": true,
"grants": [
{
"user": "9876543210987654321",
"name": "Ada Lovelace",
"email": "ada@example.com",
"capability": "edit",
"state": "active",
"created": "2026-04-27 16:37:29 UTC",
"expires": null
},
{
"user": null,
"name": null,
"email": "pending.invitee@example.com",
"capability": "view",
"state": "pending",
"created": "2026-04-27 16:40:00 UTC",
"expires": "2026-05-27 16:40:00 UTC"
}
]
}
Grant Fields
| Field | Type | Description |
|---|---|---|
| user | string | null | The grantee's 19-digit user profile ID. null for a pending invitee (no account yet). |
| name | string | null | Display name. null when unknown (a pending invitee, or no name set). |
| string | null | The grantee's email address (the real invitation email for a pending invitee). null in the rare case a claimed account has no email on record. | |
| capability | string | view, download, or edit. |
| state | string | active (a real, claimed user) or pending (an invited address not yet claimed). |
| created | string | When the grant was created (Y-m-d H:i:s UTC). |
| expires | string | null | For an active grant, the grant's own expiry; for a pending invitee, the invitation's expiry. null when it does not expire. |
Grant / Revoke (POST / DELETE) Request Body (form-encoded)
Supply exactly one of user or email (supplying both, or neither, is rejected as invalid input — 1605, HTTP 406).
| Name | Type | Required | Description |
|---|---|---|---|
| user | string | One of user/email | 19-digit user profile ID of the grantee (an existing account) |
| string | One of user/email | Email address of the grantee — lets you grant a person who may not have an account yet | |
| capability | string | Yes for POST | view, download, or edit |
A view grant can open the link and preview; download can also download the bytes (and historical versions); edit can additionally replace the file's content via an upload session (see the Upload reference). Re-granting the same capability, or revoking a non-existent grant, is a silent no-op. Response (POST by user, or DELETE): {"result": true}.
Granting by Email
- If the email belongs to an existing account, it is granted directly — the response carries the resolved
user, and the grant appears in the list asstate: active. - If the email has no account yet, a pending invitation is created and an invite email is sent. The grant appears in the list as
state: pending(withuser: null) and activates automatically when that person signs up with the invited address — they then hold exactly the capability you granted. The response carries the new pendinggrantin the same shape as a grants-list row (so it folds straight into the rendered access list). - Re-granting a pending email at a different capability updates the pending invitation to the new capability.
- Granting a now-registered email directly supersedes any pending invitation for that address (the pending entry is replaced by the active grant).
Revoking by Email (DELETE)
Revokes an active grant for that address, and/or cancels a still-pending invitation for it. Cancelling a pending invitation confers nothing, so it does not emit an access-revoked event. A DELETE that matches neither is an idempotent success.
Response (POST by registered email): { "result": true, "user": { "id": "9876543210987654321" } }
Response (POST by unregistered email — invitation created). The pending grant is returned in the same shape as a grants-list row (user is null for an unclaimed invitee; the internal invitation/account id is never exposed):
{
"result": true,
"grant": {
"user": null,
"name": null,
"email": "newperson@example.com",
"capability": "view",
"state": "pending",
"created": "2026-04-27 16:37:29 UTC",
"expires": "2026-05-27 16:37:29 UTC"
}
}
Error Responses
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 406 | user is not a valid user id / the user does not exist; email is malformed or cannot receive access; both user and email (or neither) were supplied; or capability is missing on a grant |
Workspace Discovery
List All Workspaces
/current/workspaces/all/
Lists all workspaces the user has joined or can access across all organizations.
Auth: JWT required.
Example
curl -X GET "https://api.fast.io/current/workspaces/all/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"workspaces": [
{
"id": "1234567890123456789",
"name": "Engineering Team",
"folder_name": "engineering",
"description": "Main engineering workspace",
"accent_color": "#0066CC",
"logo": "https://assets.fast.io/1234567890123456789/logo.png",
"closed": false,
"archived": false,
"perm_join": "Member or above",
"perm_member_manage": "Admin or above",
"created": "2023-01-15 10:30:00 UTC",
"updated": "2024-01-20 14:45:00 UTC",
"user_status": "joined",
"org_domain": "acme-corp"
}
]
}
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 |
Spans all organizations the user belongs to. Workspaces from orgs without active subscriptions are filtered out.
List Available Workspaces
/current/workspaces/available/
Lists workspaces the user can join but has not yet joined. Useful for discovery UI.
Auth: JWT required.
Example
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
/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 |
Example
curl -X GET "https://api.fast.io/current/workspaces/check/name/engineering/" \
-H "Authorization: Bearer {jwt_token}"
Response — Name Available (202 Accepted)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | 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) | 401 | Permission denied | Not an org member |
Checks globally across all workspaces, not just the current org. Returns
202 Accepted(not200 OK) when the name is available.
List Workspaces in Org
/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 |
Example
curl -X GET "https://api.fast.io/current/org/1000000000000000001/list/workspaces/?limit=50&offset=0" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"workspaces": [
{
"id": "1234567890123456789",
"folder_name": "engineering",
"name": "Engineering Team",
"description": "Main project workspace"
}
],
"pagination": {
"total": 5,
"limit": 50,
"offset": 0,
"has_more": false
}
}
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
/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 |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/cloud-import/enable/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"message": "Cloud import features enabled",
"cloud_import": true
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | Not admin or owner |
1695 (Upgrade Required) | 402 | Access to cloud import requires an upgraded plan. | Cloud import not included in billing plan |
1610 (Internal Error) | 500 | Failed to enable cloud import features | Internal failure |
Disable Cloud Import
/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 |
Example
curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/cloud-import/disable/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"message": "Cloud import features disabled",
"cloud_import": false
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 401 | Permission denied | Not admin or owner |
1695 (Upgrade Required) | 402 | Access to cloud import requires an upgraded plan. | Cloud import not included in billing plan |
1610 (Internal Error) | 500 | Failed to disable cloud import features | Internal failure |
Provision Provider Identity
/current/imports/workspace/{workspace_id}/identities/provision/
Creates a service account, or starts a browser OAuth connection, for the specified cloud provider — then returns immediately either way. The four providers split into two families, and which family you are in decides what happens next.
Google Drive and Box — provisioned in the background. The endpoint returns immediately with status: "provisioning". Poll the identity detail or list endpoint until status is "active" (success) or "error" (failure). The identity_email field is "provisioning-pending" while provisioning is in progress, and the instructions returned at provision time say so rather than naming an address that does not exist yet. Re-fetch the identity after it activates to get the real identity_email; the sharing instruction text is a provision-time field and is not repeated on the identity response. When you share the remote folder with that address, grant a level that can WRITE if the source will be read_write — for Box that is Editor, not Viewer. Nothing verifies the granted level at connect time: the provider is the only thing that ever checks it, and it does so at transfer time, so a read-only grant connects, activates and syncs normally and then fails the source's first write-back permanently.
Dropbox and OneDrive for Business — connected by the user, in a browser. Nothing is provisioned in the background for these. The response carries status: "provisioning" plus an authorize_url, and the identity becomes "active" only once the user completes the consent screen and your app posts the result back — see Complete a Browser OAuth Connect below. There is no address to share a folder with, because the connected account IS the access: identity_email holds a placeholder (dropbox-oauth-pending / onedrive-oauth-pending) until the connect finishes, and is then the real account address. Each user connects their own account, so one workspace can hold several independent connections to the same provider.
An identity in error or revoked state can be re-provisioned. A provisioning OAuth identity is returned as-is, without a fresh authorize_url, while its consent could still be live — so a user who abandoned the browser tab cannot immediately retry. That row is reclaimable: provision it again after about 30 minutes and a new authorize_url is issued, or revoke it and provision to recover sooner.
Auth: JWT required. The identity is owned by the caller, and the floor depends on the provider: OneDrive for Business requires Member, because the resulting delegated grant is only ever useful for creating a source, which itself requires Member. Google Drive, Box and Dropbox accept any workspace member with view access.
Request Body (form-encoded)
| Field | Type | Required | Description |
|---|---|---|---|
| provider | string | Yes | google_drive, box, onedrive_business, or dropbox. Which of these a workspace may actually connect is plan-dependent, and not every provider is offered on every plan — read the workspace's providers endpoint rather than assuming all four |
Response (200 OK)
{
"result": true,
"identity": {
"id": "abc123...",
"profile_id": "1234567890123456789",
"provider": "google_drive",
"identity_email": "provisioning-pending",
"status": "provisioning",
"created": "2026-07-23 16:37:29 UTC",
"updated": "2026-07-23 16:37:29 UTC"
},
"instructions": "This connection is still being set up. Once it reports as active, read the connection to get the address to share your folder with."
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Unsupported provider | Invalid provider name |
1680 (Access Denied) | 401 | This action requires workspace member permissions | OneDrive for Business requested below Member |
1654 (Internal Error) | 500 | Maximum of 4 provider identities per user per workspace | Limit reached |
Complete a Browser OAuth Connect
/current/imports/oauth/{provider}/complete/
Finishes a connection started by provision. {provider} is dropbox or onedrive. Google Drive and Box never use this endpoint.
After the user consents, the provider redirects the browser to a route on the app origin — https://go.{host}/imports/oauth/{provider}/callback — which is a front-end route, not an API one. That route reads code and state out of its own URL and posts them here, on the signed-in user's session. Both providers work exactly this way; there is deliberately no public callback endpoint for either, and the previous Dropbox one (GET /current/imports/oauth/dropbox/callback/) has been removed.
The session is what binds the flow to a person. A state token identifies the connection, not the human holding it, so an authorize_url that gets forwarded to a colleague would otherwise attach their cloud account to your identity — whose owner could then read, and on a read_write source write, their files. Requiring the completion to arrive on the originating user's session closes that: anyone else gets 1680 (Access Denied).
Post the decline, too. When the user refuses consent the provider sends back an error instead of a code; post { "state": …, "error": … } so the identity is marked. Without it the identity sits provisioning with nothing coming to finish it.
Authentication is checked before the state is consumed, so a session that lapsed during a slow consent returns 401 with both the state and the code still redeemable — refresh the session and post the same pair once more. State tokens are single-use and short-lived (about ten minutes, matched to the provider's own code lifetime); a replayed, expired, tampered or unknown one is rejected.
Auth: JWT required. The caller must BE the user who started the connection.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| state | string | Yes | The state value the provider handed back |
| code | string | Conditional | The authorization code. Required unless error is sent |
| error | string | Conditional | The provider's error value when the user declined. Required unless code is sent |
Response (200 OK)
{
"result": true,
"identity": {
"id": "abc123...",
"profile_id": "1234567890123456789",
"provider": "onedrive_business",
"identity_email": "casey@contoso.com",
"status": "active",
"created": "2026-08-09 16:37:29 UTC",
"updated": "2026-08-09 16:38:04 UTC"
},
"connected": true,
"connect_error_code": null
}
connected and connect_error_code report the outcome directly, so the result does not have to be discovered by polling the identity afterwards.
| connect_error_code | Meaning |
|---|---|
null | Connected; identity is active |
declined | The user refused consent |
exchange_failed | The provider would not exchange the code |
credential_store_failed | The credential could not be stored |
activation_failed | The identity could not be activated |
insufficient_scope | OneDrive only — consent omitted a permission the importer cannot work without. The response also carries an admin_consent_url a tenant administrator can open |
Request faults — a bad state, the wrong caller, an unknown identity — are ordinary 4xx errors, not a connect_error_code.
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Invalid JSON in request body | Body is not valid JSON |
1605 (Invalid Input) | 400 | Missing or invalid connection state | No state field |
1605 (Invalid Input) | 400 | This connection link has expired or was already used. Start the connection again. | Unknown, replayed, expired or tampered state |
1605 (Invalid Input) | 400 | Missing authorization code | Neither code nor error was sent |
1680 (Access Denied) | 401 | This connection was started by a different user. | The caller is not the user the state was issued to |
1680 (Access Denied) | 401 | This connection state does not match the connection it names. | The state and the identity it names disagree |
1609 (Not Found) | 404 | The connection being completed no longer exists. | The identity was removed while consent was open |
1610 (Internal Error) | 500 | This connection is no longer waiting to be completed. | The identity is no longer provisioning |
A completion that arrives for an identity already active is treated as success and returns the identity rather than an error — a double-posted callback is safe.
Revoke Provider Identity
/current/imports/workspace/{workspace_id}/identities/{identity_id}/revoke/
Begins revocation of a provider identity. The provider-side deletion happens in the background; the endpoint returns immediately with status: "revoking". Poll the identity detail or list endpoint until status is "revoked". If a revocation is already in progress for this identity, the endpoint returns an error.
Auth: JWT required. Admin or Owner required.
Response (200 OK)
{
"result": true,
"identity": {
"id": "abc123...",
"profile_id": "1234567890123456789",
"provider": "google_drive",
"identity_email": "import-sa@project.iam.gserviceaccount.com",
"status": "revoking",
"created": "2026-07-23 12:00:00 UTC",
"updated": "2026-07-23 16:37:29 UTC"
}
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1607 (Duplicate Entry) | 400 | Identity revocation already in progress | Revoke already running for this identity |
1654 (Internal Error) | 500 | Identity not found | Unknown identity ID |
Identity Statuses
| Status | Description |
|---|---|
provisioning | Google Drive and Box: creation is in progress; poll until active or error. Dropbox and OneDrive: the user has not finished the browser consent yet, and no amount of polling will advance it on its own |
active | Identity is ready; identity_email is the real address, or for Dropbox and OneDrive the connected account's address. Sharing instructions are returned by the provision response, not by this endpoint |
error | Provisioning or the browser connect failed; the identity can be re-provisioned |
revoking | Revocation is in progress; poll until revoked |
revoked | Identity has been revoked; the identity can be re-provisioned |
List Identity Drives
/current/imports/workspace/{workspace_id}/identities/{identity_id}/drives/
Lists the document libraries this identity can reach — the connected user's own OneDrive, plus the SharePoint libraries their account has access to. OneDrive for Business only — every other provider returns an error, because no other provider makes the caller choose a drive.
A OneDrive for Business identity is one connected Microsoft work or school account, and that account can usually reach several libraries with no meaningful default among them. Which library an import uses is therefore a per-source choice, made from this catalog and fixed when the source is created.
This endpoint answers from stored rows and never contacts the provider. To rebuild the catalog, call the refresh endpoint below.
Auth: JWT required. The identity owner only — for OneDrive there is no workspace-admin override, because this catalog enumerates one person's own Microsoft content. Admins keep every removal power (revoke the identity, disconnect or delete a source); what they cannot do is browse it. The other three providers' catalogs are a workspace-level service account or a shared folder, so those stay owner-or-admin.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
site_path | string | No | Return only the libraries discovered through this SharePoint site path |
limit | int | No | Page size (default 50, max 100) |
offset | int | No | Page offset (default 0) |
Response (200 OK)
{
"result": true,
"identity_id": "abc123...",
"provider": "onedrive_business",
"drives": [
{
"drive_id": "b!X9tKqL3mEkO7nQfR2sVwYzA1bCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEf",
"drive_type": "documentLibrary",
"name": "Documents",
"site_name": "Marketing",
"web_url": "https://contoso.sharepoint.com/sites/Marketing/Shared%20Documents",
"is_default": false,
"site_path": null
}
],
"drives_state": "ready",
"drives_refreshed_at": "2026-08-03 16:42:11 UTC",
"drives_error": null,
"requires_site_path": false,
"site_path": null,
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}
site_name is worth rendering: nearly every SharePoint site has a library called “Documents”, so the library name alone is often ambiguous.
Branch on drives_state, not on the length of drives. An empty catalog is the NORMAL state on a first connection — nothing is enumerated until a refresh is asked for — so drives: [] on its own cannot say whether nobody has looked yet, an enumeration is running, access was refused, or the account genuinely reaches no libraries.
| drives_state | Meaning | What the caller does |
|---|---|---|
never_refreshed | Nobody has enumerated this identity yet | Call the refresh endpoint |
refreshing | An enumeration is queued or running | Poll until it changes |
ready | The catalog holds at least one library | Let the user pick one |
empty | The enumeration completed and found no libraries | The connected account reaches none; check the account, or name a site |
requires_site_path | The enumeration could not prove it saw everything reachable, and found nothing to offer | Refresh again with site_path |
permission_denied | Something the enumeration NAMED was refused | A site path will not fix this; the account needs access, or reconnect with fuller consent |
failed | The enumeration itself failed (provider outage, token error) | Retry; drives_error carries the detail |
requires_site_path is not a permission error. There is no Microsoft API that lists every site a signed-in user can reach — only a search that is permission-trimmed and not guaranteed exhaustive. The state means “we cannot prove this list is complete, and it is empty; name a site and we will look there”. It is raised only when nothing was refused, so it is genuinely distinct from permission_denied, which is always a refusal of something specific and is not fixed by supplying a path. A scoped refresh never produces it.
requires_site_path is a convenience alias of drives_state === "requires_site_path".
A partial consent is not an error. If the user grants access to their files but withholds the SharePoint permission, the connection succeeds and the identity becomes active — the catalog simply covers their own OneDrive and no site libraries. Treat that as a working connection with a smaller catalog, not a failed one.
drives_error is normally null on success, with ONE exception: a ready catalog that was cut off at an internal ceiling carries a truncation note. Those libraries are real and selectable — the list is merely incomplete — so show it as a warning beside a usable list rather than as a failure.
Note that ready and empty are derived from the rows returned in the same response, so the state and the list can never disagree. The other five describe how the last enumeration ENDED and are reported as recorded.
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | <Provider> does not use drive selection | Provider is not OneDrive for Business |
1609 (Not Found) | 404 | Provider identity not found for this workspace | Unknown identity, or one in another workspace |
1680 (Access Denied) | 401 | Only the owner of the connected cloud account can import its folders | Not the identity owner (a workspace admin is also refused) |
Refresh Identity Drives
/current/imports/workspace/{workspace_id}/identities/{identity_id}/drives/refresh/
Re-enumerates the drive catalog from the provider. OneDrive for Business only.
Asynchronous, and it returns no job id. The response comes back immediately with drives_state: "refreshing"; poll GET .../drives/ until the state leaves refreshing. The state is recorded before the work is queued, so a client that reloads mid-refresh still sees refreshing on a cold read.
One refresh at a time per identity: a second call while one is genuinely in flight is rejected. That block expires on its own, so an interrupted refresh cannot lock the catalog permanently.
A refresh scoped with site_path replaces only the libraries belonging to that site and leaves the rest of the catalog alone, so an account whose sites have to be named one at a time can build its catalog across several calls. An unscoped refresh looks at the connected account's own OneDrive first, then the SharePoint sites it can find; it replaces only what it actually covered, so a run that found no sites does not delete the site libraries an earlier scoped refresh recorded.
An enumeration that fails part-way — a provider outage rather than a denial — leaves the previous catalog in place rather than replacing it with a partial one.
Auth: JWT required. The identity owner only — same rule as the list endpoint above, and for the same reason.
Request Body (optional)
{ "site_path": "contoso.sharepoint.com:/sites/Marketing" }
| Parameter | Type | Required | Description |
|---|---|---|---|
site_path | string | No | Enumerate one named SharePoint site instead of everything the account can find |
Response (200 OK)
{
"result": true,
"identity_id": "abc123...",
"provider": "onedrive_business",
"drives_state": "refreshing",
"drives_refreshed_at": "2026-08-03 16:40:02 UTC",
"requires_site_path": false,
"site_path": null,
"message": "Drive refresh started. Poll GET .../identities/param/abc123.../drives/ until drives_state leaves \"refreshing\"."
}
drives_refreshed_at is the PREVIOUS refresh's timestamp — this one has not finished — or null if none has ever completed. Use drives_state to detect a never-refreshed identity, not a null timestamp.
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | <Provider> does not use drive selection | Provider is not OneDrive for Business |
1609 (Not Found) | 404 | Provider identity not found for this workspace | Unknown identity, or one in another workspace |
1680 (Access Denied) | 401 | Only the owner of the connected cloud account can import its folders | Not the identity owner (a workspace admin is also refused) |
1610 (Internal Error) | 500 | A drive refresh is already in progress for this identity | A refresh is in flight |
1610 (Internal Error) | 500 | Provider identity is not active | Identity is provisioning, revoked, or errored |
Selecting a Drive on a Source
drive_id is required when creating or discovering against a OneDrive for Business identity, and is not accepted for any other provider.
- Discover (
POST .../sources/discover/) takesdrive_idso the folders it returns come from the library the user actually chose. - Create (
POST .../sources/create/) takes the samedrive_id. It is validated against that identity's own catalog, so a caller cannot reach a library it was never granted by supplying an arbitrary id. The library's type, name and site name are recorded from the stored row, never from the request. - Update (
POST /current/imports/details/{source_id}/update/) rejectsdrive_id. The library is part of what the source IS: repointing an existing source would make the recorded origin of every file it has already imported wrong. Create a second source instead.
Pass drive_id exactly as the drives endpoint returned it — it is an opaque provider string, not a Fastio id, and must not be reformatted or validated client-side.
Who may do this. Creating a source is restricted to the identity owner for every provider, with no workspace-admin override: only the person whose cloud account is connected may graft its folders into a workspace, and they must hold Member on that workspace. For OneDrive for Business, discovery is owner-only as well — it browses that person's own Microsoft content. Admins are refused here on purpose, and keep every removal power: revoking an identity, and disconnecting or deleting a source, remain owner-or-admin.
| Error Code | HTTP Status | Reason | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | drive_required | OneDrive source without a drive_id |
1605 (Invalid Input) | 400 | drive_unknown | drive_id is not in this identity's catalog |
1605 (Invalid Input) | 400 | drive_not_supported | drive_id sent for a provider that does not use one |
1605 (Invalid Input) | 400 | drive_immutable | drive_id sent to the update endpoint |
1610 (Internal Error) | 500 | drive_lookup_failed | The catalog could not be read; retry |
Browsing Below the Provider Root
POST .../sources/discover/ accepts an optional remote_path: the remote folder to enumerate. Omit it — or send an empty string, or null — to enumerate the provider root, which is the historical behaviour and stays the default.
| Parameter | Type | Required | Description |
|---|---|---|---|
remote_path | string | No | Remote folder to enumerate, e.g. /Clients/Northwind. Omit for the provider root. Maximum 2048 characters |
Discovery returns folders only, one level down. It does not walk the tree: a client expands a level at a time, passing the folder it wants to open back as remote_path on the next call. The response shape, the job id, and the polling endpoint are unchanged.
Two paths are two different questions, and they do not collide. A discovery already in flight blocks only another discovery of the same workspace, provider, drive and path — so /Clients and /Clients/Northwind can be browsed at the same time, while asking the same question twice is still deduplicated into the first.
Only the length is checked when the call is accepted. A path the provider cannot resolve is reported by the discovery job, not by this endpoint: the call returns 200 with a job id, and that job finishes failed with the reason in its error_message. Check the job, not just the HTTP status.
remote_path is also an input on POST .../sources/create/, where it names the remote folder the source imports. On discover it names the folder to browse; the two are separate calls and neither implies the other.
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | remote_path is too long | remote_path is longer than 2048 characters |
Choosing Where an Import Lands
POST .../sources/create/ accepts an optional destination_node_id: the workspace storage folder the source's imported folder is created under. Omit it — or send an empty string, or null — and the import lands in the Imports system folder at the storage root, which is the historical placement and unchanged for every existing caller. A client that always sends the key can therefore leave it null here — but not on the update endpoint below, which rejects the key whatever its value.
| Parameter | Type | Required | Description |
|---|---|---|---|
destination_node_id | string | No | Storage node id of an existing folder in this workspace. Omit for the Imports system folder |
Pass the node id exactly as a storage endpoint returned it; the formatted (dashed) form is accepted.
- The node must exist in this workspace and must be a folder. It is resolved against this workspace's own storage, so an id from another workspace is simply not found here.
- Create-time only.
POST /current/imports/details/{source_id}/update/rejectsdestination_node_id, on the key's presence rather than its value, and rejects it before applying any other field — a request carrying both a destination change and a legitimate one applies neither. Repointing a live source would leave every file it has already imported recorded against a location it is no longer in. The imported folder itself stays freely movable: move it in the workspace instead, and the sync follows it. - The source object carries the choice and its outcome — see Reading the destination back below.
root_node_idnames the folder the import actually created, and is populated once the first sync has run. - If the chosen folder has been trashed or deleted by the time the first sync runs, the import lands in the default
Importsfolder rather than failing, and the source recordsdestination_fallback_at. A degraded placement is visible and fixable; a failed first sync looks like a broken import. - An import's folder may not be created inside another import. A destination that is itself part of an import — an import's own folder, or anything beneath it — is rejected at create, with reason
destination_nested, before any source exists. Choose a folder outside every existing import. (Relatedly,POST /current/workspace/{workspace_id}/storage/{node_id}/move/refuses to move a cloud-import folder inside another one.) - A destination the server could not verify is a retry, not a rejection. The nesting check walks the folder's ancestry; if that walk cannot complete, the call answers
1610 (Internal Error)with "Could not verify the chosen destination; please try again". That means undetermined, not nested — retry the same request rather than sending the user back to pick a different folder.
Reading the destination back. Every source object — from create, list, and details alike — carries two top-level fields, alongside drive_id. Both are also present inside properties.
| Field | Type | Meaning |
|---|---|---|
destination_node_id | string | null | The folder the caller chose at create. Null means the default Imports folder |
destination_fallback_at | string | null | UTC Y-m-d H:i:s. Set when the chosen folder could not be used at graft time and the import landed in Imports instead. Null means no fallback happened |
Read them as a pair. destination_node_id on its own is the folder the user asked for, and it reads as a successful placement — which is exactly the wrong conclusion when the import actually fell back. A non-null destination_fallback_at beside it means "you asked for that folder, you got the default, at this time". A client that renders only the first will tell the user their choice was honoured when it was not.
destination_fallback_at describes placement at graft time, not current location. The imported folder is freely movable afterwards, so treat it as a record of what happened when the import landed, not as a live assertion about where it is now. It is written once and never cleared.
A destination that does not resolve is deliberately indistinguishable from one that is not yours. A malformed id, a node that does not exist, a trashed node, and a node in another workspace all answer with the same message, the same code and the same reason — "The chosen destination folder was not found in this workspace" — so the endpoint cannot be used to probe which node ids exist elsewhere. Only "the node is a file, not a folder" answers differently, and that branch is reachable only for a node the caller can already list.
| Error Code | HTTP Status | Reason | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | destination_unknown | The id is malformed, or names a node that is missing, trashed, or in another workspace |
1605 (Invalid Input) | 400 | destination_unknown | The node is a file, not a folder ("The chosen destination must be a folder, not a file") |
1605 (Invalid Input) | 400 | destination_unknown | The value is not a string (an array or object was sent) |
1605 (Invalid Input) | 400 | destination_nested | The folder is itself part of an import ("That folder is already part of an import. Choose a folder outside it.") |
1605 (Invalid Input) | 400 | destination_immutable | destination_node_id sent to the update endpoint |
1610 (Internal Error) | 500 | (none) | The nesting check could not complete ("Could not verify the chosen destination; please try again"). Undetermined, not nested — retry |
Every 1605 rejection above is field-scoped: the response carries a detail naming destination_node_id, with kind: "invalid", the human message, a support code, and the stable reason. Branch on reason, never on the message text. The 1610 case is the one exception — it is a plain error with no field detail and no reason, because it reports that the destination could not be evaluated, not that it was bad. For that branch alone the numeric code 138825 is the discriminator, and it is contract-stable — it will not be merged into another call site. Treat it as retry the same request, never as pick a different folder.