Comments API Threading, mentions, reactions, and reference anchoring for workspace and share entities.
Comments use JSON request bodies (Content-Type: application/json), unlike most other Fastio endpoints which use application/x-www-form-urlencoded.
Endpoint Summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /current/comments/{entity_type}/{parent_id}/ | List all comments in a workspace, share, or File Share |
| GET | /current/comments/{entity_type}/{parent_id}/{node_id}/ | List comments for a specific node |
| POST | /current/comments/{entity_type}/{parent_id}/ | Create or update a comment on an entity |
| POST | /current/comments/{entity_type}/{parent_id}/{node_id}/ | Create or update a comment on a node |
| POST | /current/comments/{comment_id}/update/ | Edit a comment by ID (works for every comment surface, incl. task comments) |
| GET | /current/comments/{comment_id}/details/ | Get a single comment's details |
| DELETE | /current/comments/{comment_id}/delete/ | Soft-delete a comment and its replies |
| POST | /current/comments/bulk/delete/ | Bulk soft-delete multiple comments |
| POST | /current/comments/{comment_id}/reactions/ | Add or change an emoji reaction |
| DELETE | /current/comments/{comment_id}/reactions/ | Remove an emoji reaction |
| POST | /current/comments/{comment_id}/link/ | Link a comment to a workflow entity |
| POST | /current/comments/{comment_id}/unlink/ | Unlink a comment from a workflow entity |
| GET | /current/comments/linked/{entity_type}/{entity_id}/ | Get all comments linked to a workflow entity |
| GET | /current/comments/{comment_id}/attachments/ | List a comment's attachments (hydrated, access-gated) |
| POST | /current/comments/{comment_id}/attachments/ | Attach one or many objects to a comment |
| POST | /current/comments/{comment_id}/attachments/detach/ | Detach an object from a comment |
| GET | /current/tasks/{list_id}/items/{task_id}/comments/ | List a task's comment thread |
| POST | /current/tasks/{list_id}/items/{task_id}/comments/ | Post a comment on a task |
Searching comments: To search comments by keyword, use the unified search endpoint — GET /current/workspace/{workspace_id}/search/ (or /current/share/{share_id}/search/) returns a comments bucket alongside files and other types, each with its own pagination. See Unified Search in the Storage Operations reference. Comment search results are permission-filtered to comments the caller can see. Task comments (see Task Comments below) are private to their task and are excluded from comment search.
Path Parameters
Comments are scoped to a workspace, share, or File Share, and optionally to a specific node (file or folder) within it. When no node ID is specified, all comments across the entire workspace or share are returned.
| Parameter | Type | Format | Description |
|---|---|---|---|
| {entity_type} | string | "workspace", "share", or "fileshare" |
Entity type discriminator |
| {parent_id} | string | 19-digit numeric | Workspace or share profile ID. Note: Do not confuse with parent_id in the POST request body, which is the parent comment ID for threading. |
| {node_id} | string | Alphanumeric opaque ID | File or folder ID within the entity (optional) |
| {comment_id} | string | Alphanumeric opaque ID | Comment identifier |
File Share comments. When {entity_type} is "fileshare", {parent_id} is the File Share's 19-digit numeric profile ID. Comments on a File Share are scoped to that File Share — a recipient sees only the comments made under it, never comments on the same underlying file from the owning workspace or from another File Share. Commenting requires the viewer to be signed in; public anyone-with-link visitors cannot comment. Comments are available only when the File Share owner has enabled comments, and they are always disabled on public anyone-with-link shares. A client should read the File Share's effective comments_enabled flag from its details response to decide whether to show the comment UI.
Comment Object
Every comment returned by the API has this structure:
| Field | Type | Description |
|---|---|---|
| id | string | Alphanumeric opaque ID of the comment |
| entity | string | Opaque ID of the entity the comment belongs to (workspace, share, File Share, or node) |
| user_id | string | 19-digit numeric ID of the comment author |
| parent_id | string|null | Opaque ID of the parent comment (for replies), or null for top-level |
| body | string | Comment text content (may include mention markup) |
| profile_type | string|null | The container type ("workspace", "share", or "fileshare") that owns the entity |
| profile_id | string|null | 19-digit numeric ID of the workspace, share, or File Share that owns the entity |
| scope_id | string|null | When a comment originated under a File Share and is surfaced in a workspace context, the 19-digit numeric ID of that File Share — a present (non-null) value marks the comment as "via File Share". Null/omitted for comments that originated directly in the workspace or share |
| linked_entity_type | string|null | If linked to a workflow entity, one of "task", "workflow_review", or "approval"; otherwise null. "approval" is legacy-accepted for backward compatibility; new links should use "workflow_review" |
| linked_entity_id | string|null | Opaque ID of the linked workflow entity, or null |
| reference | object|null | Anchoring reference to a position in a file (see Reference Anchoring) |
| mentions | array | The server-validated set of mentioned profile IDs that took effect (19-digit numeric strings). Always present; empty when no mention took effect. This is the authoritative took-effect set — mentions in the body markup that fail validation (e.g. a non-member) are excluded here |
| reactions | object | Map of emoji character to total reaction count (e.g., {"👍": 3}) |
| user_reaction | string|null | The current authenticated user's emoji reaction, or null |
| version_hash | string|null | Hash representing the current comment content version |
| version | integer | Monotonically increasing version counter (starts at 1) |
| attachment_count | integer | Number of objects attached to the comment (see Comment Attachments). Present on comment list rows and the comment detail response |
| attachments | array | Hydrated, access-gated attachment rows (see Comment Attachments). Detail endpoint only |
| edited_at | string|null | Timestamp of last edit if the comment has been edited (YYYY-MM-DD HH:MM:SS UTC), or null |
| can_edit | boolean | Whether the requesting user can edit this comment |
| can_delete | boolean | Whether the requesting user can delete this comment |
| can_reply | boolean | Whether the comment can be replied to (top-level only) |
| created | string | Creation timestamp (YYYY-MM-DD HH:MM:SS UTC) |
| updated | string | Last-updated timestamp (YYYY-MM-DD HH:MM:SS UTC) |
| deleted | string|null | Deletion timestamp (YYYY-MM-DD HH:MM:SS UTC), or null if active. Only included on the get-details endpoint or when explicitly requesting deleted comments. |
Note: the server-validated mentions set is surfaced as the top-level mentions array described above. The raw internal properties object (threading, pinning, content-filter flags, etc.) is never returned — only the curated mentions list is exposed.
List Comments
/current/comments/{entity_type}/{parent_id}/
List all comments across the entire workspace or share.
/current/comments/{entity_type}/{parent_id}/{node_id}/
List comments for a specific node within an entity.
Without {node_id}: returns all comments across the entire workspace or share. With {node_id}: returns only comments on that specific file or folder.
Note: Only comments created or edited after 2026-03-15 are included in scope-level results (without {node_id}); older comments are accessible via the node-level endpoint.
Auth: Required (JWT). Rate limited.
Query Parameters
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
| sort | string | No | asc |
"asc" or "desc" |
Sort order by creation time |
| limit | integer | No | — | Min: 2, Max: 200 | Number of comments to return |
| offset | integer | No | 0 |
Min: 0 | Number of comments to skip |
| page | integer | No | — | Min: 1 | Page number (alternative to offset-based pagination) |
| include_deleted | boolean | No | false |
— | Include soft-deleted comments in results |
| reference_type | string | No | — | — | Filter by reference anchor type (e.g., "page", "timestamp") |
| include_total | boolean | No | false |
— | Include total count and pagination metadata in response |
Request Example
curl -X GET "https://api.fast.io/current/comments/workspace/1234567890123456789/?limit=50&include_total=true" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"comments": [
{
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"reference": null,
"mentions": [],
"reactions": {"👍": 2, "❤️": 1},
"user_reaction": "👍",
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC"
}
],
"count": 1,
"allowed": true,
"remaining": 95,
"total": 5,
"limit": 50,
"offset": 0
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.comments | array | Array of comment objects |
| response.count | int | Number of comments in this response |
| response.allowed | bool | Whether the current user can post new comments (based on plan limits) |
| response.remaining | int | Remaining comments allowed under plan limit (only present if plan has a limit) |
| response.total | int | Total number of comments (only if include_total=true) |
| response.limit | int | Limit used in query (only if include_total=true) |
| response.offset | int | Offset used in query (only if include_total=true) |
Access Levels
| Role | Access |
|---|---|
| Workspace Owner/Member | Full access — sees all comments |
| Share Owner | Full access — sees all comments |
| Share Guest | Filtered — sees own comments; visibility of owner and other guest comments depends on share permissions |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Invalid API format... | Missing entity type or parent ID |
1605 (Invalid Input) | 400 | Entity type must be "workspace" or "share" | Invalid entity type |
1680 (Access Denied) | 403 | Invalid entity or insufficient permissions | User lacks access |
1609 (Not Found) | 404 | Invalid entity or insufficient permissions | Entity not found |
1654 (Internal Error) | 500 | Failed to retrieve comments | Internal error |
Workspace node reads include File Share comments. A file shared through a File Share is the same node that lives in the owning workspace, so a workspace node read (GET /current/comments/workspace/{workspace_id}/{node_id}/) also returns comments left by File Share recipients on that file. Those File Share comments appear read-only in the workspace view: can_reply, can_edit, and can_delete are false (replies and edits to a File Share comment go through the File Share's own surface). The total count reflects everything visible in the listing (the workspace's own comments plus the File Share comments), while the allowed / remaining fields reflect only the workspace's own writable comments — so remaining is not reduced by File Share comments you cannot edit. This inclusion is one-directional: workspace members see File Share comments, but File Share recipients never see the workspace's internal comments.
Create or Update Comment
/current/comments/{entity_type}/{parent_id}/
Create a new comment or update an existing one on an entity.
/current/comments/{entity_type}/{parent_id}/{node_id}/
Create a new comment or update an existing one on a specific node.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| body | string | Required | 1–8192 chars (raw); display text excluding mentions max 500 chars | Comment text content |
| comment_id | string | No | Valid opaque ID | Include to update an existing comment; omit to create new |
| parent_id | string | No | Valid opaque ID | Parent comment ID for threaded reply (single-level only). Note: This is the parent comment's opaque ID, not the workspace/share ID from the URL path {parent_id}. |
| properties | object | No | — | Arbitrary key-value metadata to attach |
| reference | object | No | See Reference Anchoring | Anchoring reference to a position in a file |
| target_id | string | No | Valid object ID | Inline attachment on a new comment — a single object to attach (see Comment Attachments). Ignored on update |
| target_ids | array<string> | No | Non-empty array | Inline attachments on a new comment — multiple objects to attach, up to 25 per comment. Ignored on update |
Request Example — Create a new comment
curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"body": "Great work on this document!"
}'
Request Example — Reply to a comment
curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"body": "Thanks for the feedback!",
"parent_id": "abc123opaqueid"
}'
Request Example — Comment with mention and page reference
curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"body": "Hey @[user:9876543210987654321:Jane Smith], can you review page 3?",
"reference": {"type": "page", "page": 3}
}'
Request Example — Update an existing comment
curl -X POST "https://api.fast.io/current/comments/workspace/1234567890123456789/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"comment_id": "def456opaqueid",
"body": "Updated: This looks great after the revision."
}'
Response
{
"result": true,
"comment": {
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "Great work on this document!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"reference": null,
"reactions": {},
"user_reaction": null,
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.comment | object | The created or updated comment object (full schema above) |
Access Levels
| Role | Access |
|---|---|
| Workspace Owner/Member | Can create and edit own comments |
| Share Owner | Can create and edit own comments |
| Share Guest | Can only post if comments are enabled on the share |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 403 | Authentication required to post comments | User not authenticated |
1605 (Invalid Input) | 400 | Invalid JSON in request body | Malformed JSON |
1605 (Invalid Input) | 400 | Comment body must be between 1 and 8192 characters | Body length out of range |
1605 (Invalid Input) | 400 | Comment text (excluding mentions) must not exceed 500 characters | Display text too long |
1605 (Invalid Input) | 400 | Your comment appears to contain spam content... | Spam detected |
1605 (Invalid Input) | 400 | Invalid parent comment ID format | Malformed parent_id |
1609 (Not Found) | 404 | Parent comment not found | Referenced parent does not exist |
1605 (Invalid Input) | 400 | Parent comment belongs to different entity | Parent is on a different entity |
1605 (Invalid Input) | 400 | Invalid reference data: ... | Reference failed validation |
1605 (Invalid Input) | 400 | Comment limit exceeded for your plan | Plan comment limit reached |
1609 (Not Found) | 404 | Comment not found | Comment ID for update not found |
1680 (Access Denied) | 403 | You do not have permission to edit this comment | User is not the comment author |
1654 (Internal Error) | 500 | Failed to save comment | Internal error |
Notes
- Content filtering: Bodies are sanitized (HTML stripped) and filtered for profanity, spam, and PII. Spam is blocked; other detected categories result in the body being modified before storage.
- Mentions: Mentioned users are parsed and validated against entity membership before the comment is saved.
- Plan limits: Number of comments per entity is limited by the organization's plan.
- Updating: Only the original author can edit. Editing populates the top-level
edited_attimestamp on the returned comment.
Update Comment by ID
/current/comments/{comment_id}/update/
Edit an existing comment by its ID. Author-only. Works for every comment surface — workspace, share, node, File Share, and task comments. (The entity-scoped update above cannot address a task comment, whose entity is the task itself — use this endpoint.)
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| body | string | Required | 1–8192 chars (raw); display text excluding mentions max 500 chars | Replacement comment text |
| properties | object | No | — | Arbitrary key-value metadata to merge |
| reference | object | No | See Reference Anchoring | Replacement anchoring reference |
Request Example
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/update/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"body": "Updated: this looks great after the revision."
}'
Response
The full updated comment object (same shape as Create or Update Comment above), with edited_at populated.
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Comment body must be between 1 and 8192 characters | Body length out of range |
1605 (Invalid Input) | 400 | Comment is deleted | The comment has been deleted |
1609 (Not Found) | 404 | Comment not found | Unknown comment ID, or the comment's surface is not visible to you |
1680 (Access Denied) | 403 | You do not have permission to edit this comment | Caller is not the comment author |
Notes
- Only the original author can edit; mentions are re-validated from the edited body and the comment's
edited_attimestamp is populated. - An edit can never move the comment: its entity, scope, threading, and task/workflow links are immutable here.
- For a task comment, access additionally requires task access (Workflow feature enabled, not a share guest, task not trashed) — the same gating as the task's comment thread.
Get Comment Details
/current/comments/{comment_id}/details/
Get a single comment's full details.
Auth: Required (JWT). Rate limited.
Request Example
curl -X GET "https://api.fast.io/current/comments/abc123opaqueid/details/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"comment": {
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"reference": null,
"reactions": {},
"user_reaction": null,
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC",
"deleted": null
}
}
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1680 (Access Denied) | 403 | You do not have permission to view this comment | User lacks access to the entity |
Delete Comment
/current/comments/{comment_id}/delete/
Soft-delete a comment. Recursive — also deletes all replies to this comment.
Who can delete: the comment's author, and workspace/share admins or owners (moderation — admins can remove any comment on their workspace or share). File Share comments can only be deleted by their author; they are never moderator-deletable, including from the workspace view. The can_delete flag on every returned comment reflects this, so clients should drive the delete affordance from can_delete.
Auth: Required (JWT). Rate limited.
Request Example
curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/delete/" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"message": "Comment deleted successfully",
"comment": {
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"reference": null,
"reactions": {},
"user_reaction": null,
"version": 1,
"can_edit": false,
"can_delete": false,
"can_reply": false,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-16 08:00:00 UTC",
"deleted": "2025-01-16 08:00:00 UTC"
}
}
Access Levels
| Role | Access |
|---|---|
| Comment Author | Can delete own comments |
| Entity Owner | Can delete any comment on their entity |
| Other Users | Denied |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1605 (Invalid Input) | 400 | Comment is already deleted | Comment was previously deleted |
1680 (Access Denied) | 403 | You do not have permission to delete this comment | Not author or entity owner |
1654 (Internal Error) | 500 | Failed to delete comment | Internal error |
Bulk Delete Comments
/current/comments/bulk/delete/
Bulk soft-delete multiple comments. Each comment is processed independently — partial success is possible. NOT recursive — does not delete replies.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| comment_ids | array<string> | Required | Max 100 items | Array of comment opaque IDs to delete |
Request Example
curl -X POST "https://api.fast.io/current/comments/bulk/delete/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"comment_ids": ["abc123opaqueid", "def456opaqueid", "ghi789opaqueid"]}'
Response
{
"result": true,
"success": true,
"deleted_count": 3,
"failed_count": 0,
"total_count": 3,
"results": {
"abc123opaqueid": {"id": "abc123opaqueid", "success": true, "error": null},
"def456opaqueid": {"id": "def456opaqueid", "success": true, "error": null},
"ghi789opaqueid": {"id": "ghi789opaqueid", "success": true, "error": null}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.success | bool | true only if all comments were deleted (failed_count === 0) |
| response.deleted_count | int | Number of successfully deleted comments |
| response.failed_count | int | Number that failed to delete |
| response.total_count | int | Total number of IDs provided |
| response.results | object | Per-comment results keyed by comment ID |
| response.results.{id}.success | bool | Whether this comment was deleted |
| response.results.{id}.error | string|null | Error message if failed, null on success |
Per-Comment Error Messages
| Error | Cause |
|---|---|
| Invalid comment ID format | Not a valid opaque ID |
| Comment not found | No comment with this ID |
| Comment already deleted | Previously soft-deleted |
| Permission denied | User is not author or entity owner |
| Failed to delete comment | Internal error |
Request-Level Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Invalid JSON request body | Malformed JSON |
1605 (Invalid Input) | 400 | comment_ids array is required | Missing or non-array field |
1605 (Invalid Input) | 400 | comment_ids array cannot be empty | Empty array |
1605 (Invalid Input) | 400 | Maximum 100 comments can be deleted at once | Exceeds bulk limit |
Important: Unlike single delete, bulk delete does not recursively delete replies.
Add or Change Reaction
/current/comments/{comment_id}/reactions/
Add an emoji reaction to a comment. One reaction per user per comment — sending a new reaction replaces any previous one.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| emoji | string | Required | Single emoji character; max 2 UTF-8 characters; must match Unicode emoji ranges | Emoji to react with |
Request Example
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"emoji": "👍"}'
Response
{
"result": true,
"reactions": {"👍": 3, "❤️": 1},
"user_reaction": "👍"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.reactions | object | Map of emoji to total reaction count across all users |
| response.user_reaction | string|null | The current user's active reaction emoji |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist or is deleted |
1680 (Access Denied) | 403 | You do not have permission to react to this comment | User lacks entity access |
1605 (Invalid Input) | 400 | emoji parameter is required | Missing or non-string emoji |
1605 (Invalid Input) | 400 | Invalid emoji character | Does not match Unicode emoji pattern |
1605 (Invalid Input) | 400 | Only single emoji allowed | String exceeds 2 UTF-8 characters |
1654 (Internal Error) | 500 | Failed to save reaction | Internal error |
Remove Reaction
/current/comments/{comment_id}/reactions/
Remove an emoji reaction from a comment.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| emoji | string | No | Must match Unicode emoji ranges if provided | Specific emoji to remove. Omit to remove any reaction by the current user |
Request Example — Remove specific emoji
curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"emoji": "👍"}'
Request Example — Remove any reaction
curl -X DELETE "https://api.fast.io/current/comments/abc123opaqueid/reactions/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{}'
Response
{
"result": true,
"reactions": {"❤️": 1},
"user_reaction": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.reactions | object | Updated map of emoji to total reaction count |
| response.user_reaction | string|null | Current user's reaction after removal (null if removed) |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist or is deleted |
1680 (Access Denied) | 403 | You do not have permission to react to this comment | User lacks entity access |
1605 (Invalid Input) | 400 | emoji must be a string | Non-string emoji parameter |
1605 (Invalid Input) | 400 | Invalid emoji character | Does not match Unicode emoji pattern |
1654 (Internal Error) | 500 | Failed to remove reaction | Internal error |
Notes: Removing a reaction that does not exist returns success (idempotent). A "removed" event is triggered only when a reaction was actually removed.
Link Comment to Workflow Entity
/current/comments/{comment_id}/link/
Link a comment to a workflow entity such as a task or workflow review. This creates an association between the comment and the target entity, enabling reverse lookups and workflow integration.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| entity_type | string | Required | One of "task", "workflow_review", or "approval" |
The type of workflow entity to link to. "approval" is legacy-accepted for backward compatibility; new links should use "workflow_review" |
| entity_id | string | Required | Valid alphanumeric opaque ID | The ID of the workflow entity to link to |
Request Example
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/link/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "task",
"entity_id": "task789opaqueid"
}'
Response
{
"result": true,
"comment": {
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"linked_entity_type": "task",
"linked_entity_id": "task789opaqueid",
"reference": null,
"reactions": {},
"user_reaction": null,
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.comment | object | The updated comment object with the link applied |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 403 | Authentication required to link comments | User not authenticated |
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID in path |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed comment opaque ID |
1605 (Invalid Input) | 400 | Invalid JSON in request body | Malformed JSON |
1605 (Invalid Input) | 400 | entity_type is required | Missing or non-string entity_type |
1605 (Invalid Input) | 400 | entity_type must be one of: task, workflow_review, approval | Invalid entity type |
1605 (Invalid Input) | 400 | entity_id is required | Missing or non-string entity_id |
1605 (Invalid Input) | 400 | Invalid entity_id format | Malformed entity opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1680 (Access Denied) | 403 | You do not have permission to link this comment | User lacks access to the comment's entity |
1609 (Not Found) | 404 | Target entity not found | The workflow entity to link to does not exist |
1605 (Invalid Input) | 400 | Invalid link parameters | Link validation failed |
1654 (Internal Error) | 500 | Failed to link comment | Internal error |
Unlink Comment from Workflow Entity
/current/comments/{comment_id}/unlink/
Remove the link between a comment and its associated workflow entity. After unlinking, the comment still exists but is no longer associated with any workflow entity.
Auth: Required (JWT). Rate limited.
Content-Type: application/json
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| entity_id | string | No | Valid alphanumeric opaque ID | The ID of the linked entity (recommended for optimal performance; not strictly required) |
Request Example
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/unlink/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"entity_id": "task789opaqueid"
}'
Request Example — Without entity_id
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/unlink/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{}'
Response
{
"result": true,
"comment": {
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"reference": null,
"reactions": {},
"user_reaction": null,
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.comment | object | The updated comment object with the link removed |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1680 (Access Denied) | 403 | Authentication required to unlink comments | User not authenticated |
1605 (Invalid Input) | 400 | Comment ID is required | Missing comment ID in path |
1605 (Invalid Input) | 400 | Invalid comment ID format | Malformed comment opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1680 (Access Denied) | 403 | You do not have permission to unlink this comment | User lacks access to the comment's entity |
1605 (Invalid Input) | 400 | Comment is not linked to any entity | Comment has no existing link |
1654 (Internal Error) | 500 | Failed to unlink comment | Internal error |
Get Linked Comments
/current/comments/linked/{entity_type}/{entity_id}/
Reverse lookup: retrieve all comments that have been linked to a specific workflow entity (task, workflow_review, or approval).
Auth: Required (JWT). Rate limited.
Path Parameters
| Parameter | Type | Format | Description |
|---|---|---|---|
| {entity_type} | string | One of "task", "workflow_review", or "approval" |
The type of workflow entity. "approval" is legacy-accepted for backward compatibility; new links should use "workflow_review" |
| {entity_id} | string | Alphanumeric opaque ID | The ID of the workflow entity |
Query Parameters
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
| limit | integer | No | 50 | Min: 1, Max: 200 | Number of comments to return |
| offset | integer | No | 0 | Min: 0 | Number of comments to skip |
Request Example
curl -X GET "https://api.fast.io/current/comments/linked/task/task789opaqueid/?limit=25&offset=0" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"result": true,
"comments": [
{
"id": "abc123opaqueid",
"entity": "xyz789opaqueid",
"user_id": "1234567890123456789",
"parent_id": null,
"body": "This looks great!",
"profile_type": "workspace",
"profile_id": "1234567890123456789",
"linked_entity_type": "task",
"linked_entity_id": "task789opaqueid",
"reference": null,
"reactions": {"👍": 2},
"user_reaction": "👍",
"version": 1,
"can_edit": true,
"can_delete": true,
"can_reply": true,
"created": "2025-01-15 10:30:00 UTC",
"updated": "2025-01-15 10:30:00 UTC"
}
],
"count": 1,
"entity_type": "task",
"entity_id": "task789opaqueid",
"limit": 25,
"offset": 0
}
Response Fields
| Field | Type | Description |
|---|---|---|
| response.comments | array | Array of comment objects linked to the entity |
| response.count | int | Number of comments in this response |
| response.entity_type | string | The entity type that was queried |
| response.entity_id | string | The entity ID that was queried |
| response.limit | int | Limit used in the query |
| response.offset | int | Offset used in the query |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | entity_type is required in URL path | Missing entity type |
1605 (Invalid Input) | 400 | entity_type must be one of: task, workflow_review, approval | Invalid entity type |
1605 (Invalid Input) | 400 | entity_id is required in URL path | Missing entity ID |
1605 (Invalid Input) | 400 | Invalid entity_id format | Malformed opaque ID |
1654 (Internal Error) | 500 | Failed to retrieve linked comments | Internal error |
Comment Attachments
Attach arbitrary platform objects to a comment to give it context — a file or folder, a workflow, a sign envelope, a share, a File Share, a workspace, or a task. An attachment is a lightweight reference: it records what is attached, not a copy of it.
- A comment can hold up to 25 attachments.
- Attaching is idempotent — re-attaching an object already attached is a no-op and does not count against the cap.
- Attaching does not verify the target exists or is accessible; display names are resolved (and access-gated) on read.
- Attaching/detaching is author-only — only the comment's author can add or remove attachments (a moderator who can delete a comment cannot mutate its attachments).
- Attachments are not supported on workflow-review comments (a
400is returned), and a soft-deleted comment returns404.
Attachment Object (hydrated)
Every attachment returned by the API is hydrated and access-gated to the caller: an object the caller cannot see (or that no longer exists) is returned with available: false and display_name: null rather than leaking its name. Render defensively: when available is false, never show a name.
| Field | Type | Description |
|---|---|---|
| target_id | string | The attached object's ID (19-digit numeric for a workspace/share/workflow/envelope/File Share, or an opaque ID for a file/folder node or task) |
| target_type | string | Coarse object type: node, workflow, envelope, share, workspace, fileshare, or task |
| kind | string | Alias of target_type (same value) |
| display_name | string|null | The object's resolved display name, or null when the caller cannot see it or it has no name |
| available | bool | true when the caller can access the object (and display_name is authoritative); false when it is inaccessible, missing, or could not be resolved |
List Comment Attachments
/current/comments/{comment_id}/attachments/
List all objects attached to a comment, hydrated and access-gated to the caller.
Auth: Required (JWT). Rate limited.
Response
{
"result": true,
"comment_id": "abc123opaqueid",
"attachments": [
{
"target_id": "n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5",
"target_type": "node",
"kind": "node",
"display_name": "spec.pdf",
"available": true
},
{
"target_id": "9876543210987654321",
"target_type": "share",
"kind": "share",
"display_name": null,
"available": false
}
],
"count": 2
}
Attach Objects to a Comment
/current/comments/{comment_id}/attachments/
Attach one object (target_id) or many (target_ids) to a comment. Idempotent; the new objects are committed atomically. Returns the full, updated hydrated attachment list.
Auth: Required (JWT). Rate limited. Content-Type: application/json.
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| target_id | string | One of target_id / target_ids | Valid object ID | A single object to attach |
| target_ids | array<string> | One of target_id / target_ids | Non-empty array | Multiple objects to attach |
A request whose new attachments would push the comment over the 25-attachment cap is rejected before anything is written.
Request Example — single
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/attachments/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"target_id": "n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5"}'
Request Example — bulk
curl -X POST "https://api.fast.io/current/comments/abc123opaqueid/attachments/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"target_ids": ["n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5", "9876543210987654321"]}'
Response
{
"result": true,
"comment_id": "abc123opaqueid",
"attached": 1,
"attachments": [
{
"target_id": "n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5",
"target_type": "node",
"kind": "node",
"display_name": "spec.pdf",
"available": true
}
],
"count": 1
}
Response Fields
| Field | Type | Description |
|---|---|---|
| comment_id | string | The comment the attachments belong to |
| attached | int | Number of objects newly attached by this request (0 when every target was already attached) |
| attachments | array | The full hydrated attachment list after the operation |
| count | int | Number of attachments in the list |
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 400 | A target_id or non-empty target_ids array is required | No target supplied |
1605 (Invalid Input) | 400 | Invalid attachment target | A target ID could not be decoded |
1605 (Invalid Input) | 400 | Attachment limit reached (maximum 25 per comment) | The new attachments would exceed the cap |
1605 (Invalid Input) | 400 | Attachments are not supported on workflow-review comments | Comment is part of a workflow review |
1680 (Access Denied) | 403 | You do not have permission to modify attachments on this comment | Caller is not the comment author |
1609 (Not Found) | 404 | Comment not found | Comment does not exist, is not visible to you, or is trashed |
Detach an Object from a Comment
/current/comments/{comment_id}/attachments/detach/
Remove an attachment by its target_id. Idempotent — detaching an object that is not attached returns success with removed: false. Returns the updated hydrated attachment list.
Auth: Required (JWT). Rate limited. Content-Type: application/json.
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
| target_id | string | Yes | Valid object ID | The object to detach |
Response
{
"result": true,
"comment_id": "abc123opaqueid",
"removed": true,
"attachments": [],
"count": 0
}
Response Fields
| Field | Type | Description |
|---|---|---|
| comment_id | string | The comment the attachment belonged to |
| removed | bool | true if an attachment was actually removed; false if the object was not attached |
| attachments | array | The full hydrated attachment list after the operation |
| count | int | Number of attachments remaining |
Attaching at comment-create time (inline)
You can attach objects when creating a comment by including target_id or target_ids in the comment-create body (see Create or Update Comment). Inline attachments apply only to a new comment — they are ignored on an update, and the comment plus its attachments are written together so the comment never appears without them. An invalid target or one that would exceed the cap rejects the whole create.
attachment_count is returned on every comment list row and on the comment detail response. The hydrated attachments array is returned only on the comment detail response (GET /current/comments/{comment_id}/details/). All counts are computed live on each read.
Task Comments
Tasks have their own dedicated comment thread, surfaced under the task path rather than the generic comment paths. A task comment is a normal comment object (same schema, threading, mentions, reactions, and reference anchoring as above) — only the list/create surface differs.
| Method | Path | Description |
|---|---|---|
| GET | /current/tasks/{list_id}/items/{task_id}/comments/ | List the task's comment thread (paginated; limit min 2, max 200; offset) |
| POST | /current/tasks/{list_id}/items/{task_id}/comments/ | Post a comment on the task (body, optional parent_id for a reply, optional reference, mentions parsed from body) |
Privacy. Task comments are private to their task: they are not returned by the general comment listing (GET /current/comments/{entity_type}/{parent_id}/) and not included in comment search. They surface only through the task comment path above.
Reactions, edit, and delete of a task comment use the existing comment-by-id endpoints in this reference — POST /current/comments/{comment_id}/reactions/, DELETE /current/comments/{comment_id}/reactions/, POST /current/comments/{comment_id}/update/ (edit), and DELETE /current/comments/{comment_id}/delete/.
The task create endpoint never edits. A POST to the task comments path whose body includes a comment_id field is rejected with 400 Invalid Input (it does not create a duplicate); trailing path segments after …/comments/ return 404. Edits go through POST /current/comments/{comment_id}/update/ only.
Access. The task's workflow feature must be enabled on the owning workspace or share. On shares, member or admin access is required — guests are blocked. A soft-deleted task returns 404. Mentions are validated against the task's workspace/share membership.
The full request/response shapes for the task comment list and create endpoints are documented in the Workflow (Task Management) reference under Task Comments.
Threading
Comments support single-level threading only.
- Set
parent_idto the comment you are replying to. - Replies to a top-level comment appear as children of that comment.
- Replies to a reply are auto-flattened — they become siblings of the original reply (children of the same top-level parent). The API does not support nested threading beyond one level.
Comment A (top-level)
├── Comment B (reply to A)
├── Comment C (reply to B → auto-flattened to reply to A)
└── Comment D (reply to A)
Mentions
Use mention markup in the body field to tag other users:
@[user:USER_ID:Display Name]
| Component | Description |
|---|---|
user | Literal prefix (always user) |
USER_ID | The user's 19-digit numeric profile ID |
Display Name | Display name to show in the UI |
Character limit details
- The full mention tag markup (e.g.,
@[user:1234567890123456789:Jane Smith]) counts toward the 8192-character body limit. - Display text (excluding all mention markup) is separately limited to 500 characters.
- Mentioned users are validated against entity membership at save time. Invalid mentions (users without access to the entity) are dropped silently.
Example body with mentions
Hey @[user:1234567890123456789:Jane Smith] and @[user:9876543210987654321:Bob Jones], please review this file.
Reference Anchoring
Comments can be anchored to specific positions within a file using the reference object.
Reference object fields
| Field | Type | Description |
|---|---|---|
| type | string | Required. The anchor type: "page", "timestamp", "region", "text" |
| timestamp | number | For video/audio — position in seconds |
| page | integer | For documents — page number |
| region | object | For images — coordinates {x, y, width, height} defining a rectangular area |
| text_snippet | string | For text files — the referenced text passage |
Example — Video timestamp
{
"body": "The transition at this point needs work.",
"reference": {"type": "timestamp", "timestamp": 42.5}
}
Example — Document page
{
"body": "Typo in the second paragraph.",
"reference": {"type": "page", "page": 7}
}
Example — Image region
{
"body": "This area needs higher contrast.",
"reference": {"type": "region", "region": {"x": 120, "y": 80, "width": 200, "height": 150}}
}
Example — Text snippet
{
"body": "This sentence should be rephrased.",
"reference": {"type": "text", "text_snippet": "The quick brown fox jumps over the lazy dog."}
}
Content Filtering
Comment bodies undergo automatic filtering:
- HTML sanitization: All HTML tags are stripped.
- Spam detection: Comments identified as spam are blocked entirely (400 error).
- Profanity/PII filtering: Detected content is modified in-place before storage; the returned
bodyreflects the post-filter content.