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 or share |
| GET | /current/comments/{entity_type}/{parent_id}/{node_id}/ | List comments for a specific node |
| POST | /current/comments/{entity_type}/{parent_id}/ | Create or update a comment on an entity |
| POST | /current/comments/{entity_type}/{parent_id}/{node_id}/ | Create or update a comment on a node |
| GET | /current/comments/fileshare/{fileshare_id}/{node_id}/ | List a File Share recipient's comments on a node |
| POST | /current/comments/fileshare/{fileshare_id}/{node_id}/ | Create or update a comment as a File Share recipient |
| POST | /current/comments/{comment_id}/update/ | Edit a comment by ID (works for every comment surface) |
| 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 |
| 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 |
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.
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. For "fileshare" the {node_id} segment is required (see File Share Comments). |
| {parent_id} | string | 19-digit numeric | Workspace, share, or File 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 for workspace/share, required for fileshare) |
| {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, or node) |
| user_id | string|null | 19-digit numeric ID of the comment author. null for externally-authored comments (e.g. a File Share recipient) — in that case the external_author_* fields below identify the 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" or "share") that owns the entity |
| profile_id | string|null | 19-digit numeric ID of the workspace or 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 |
| external_author_email | string|null | Email of an external author (File Share recipient), when the comment was not authored by a member; otherwise omitted/null |
| external_author_name | string|null | Display name of the external author, when present; otherwise omitted/null |
| external_author_token_id | string|null | Opaque ID of the access token the external author used, when present; otherwise omitted/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 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: "video", "image", "document", "general", or "audio" (see Reference Anchoring) |
| 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) | 406 | Invalid API format... | Missing entity type or parent ID |
1605 (Invalid Input) | 406 | Entity type must be "workspace", "share", or "fileshare" | Invalid entity type |
1680 (Access Denied) | 401 | Invalid entity or insufficient permissions | User lacks access |
1609 (Not Found) | 404 | Invalid entity or insufficient permissions | Entity not found |
1610 (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": "document", "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) | 401 | Authentication required to post comments | User not authenticated |
1605 (Invalid Input) | 406 | Invalid JSON in request body | Malformed JSON |
1605 (Invalid Input) | 406 | Comment body must be between 1 and 8192 characters | Body length out of range |
1605 (Invalid Input) | 406 | Comment text (excluding mentions) must not exceed 500 characters | Display text too long |
1605 (Invalid Input) | 406 | Your comment appears to contain spam content... | Spam detected |
1605 (Invalid Input) | 406 | Invalid parent comment ID format | Malformed parent_id |
1609 (Not Found) | 404 | Parent comment not found | Referenced parent does not exist |
1605 (Invalid Input) | 406 | Parent comment belongs to different entity | Parent is on a different entity |
1605 (Invalid Input) | 406 | Invalid reference data: ... | Reference failed validation |
1605 (Invalid Input) | 406 | 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) | 401 | You do not have permission to edit this comment | User is not the comment author |
1609 (Not Found) | 404 | Linked entity not found | Task to link is missing, soft-deleted, cross-tenant, or inaccessible |
1610 (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.
File Share Comments
/current/comments/fileshare/{fileshare_id}/{node_id}/
List a File Share recipient's comments on a node.
/current/comments/fileshare/{fileshare_id}/{node_id}/
Create or update a comment as a File Share recipient.
fileshare is a third {entity_type} on the comment surface, used by File Share recipients to read and post comments on a file inside a File Share. It shares the request/response shapes of the workspace/share List and Create endpoints above — the only differences are the fileshare discriminator and that the {node_id} segment is required (a File Share comment always targets a specific node, never the whole container).
Auth: Required. The caller must have access to the File Share (a signed-in recipient); anonymous callers are denied. Comments left here are the File Share recipient's own comments on the node.
Path Parameters
| Parameter | Type | Format | Description |
|---|---|---|---|
| {fileshare_id} | string | 19-digit numeric | File Share profile ID |
| {node_id} | string | Alphanumeric opaque ID | The file or folder node within the File Share. Required |
Request Example — list
curl -X GET "https://api.fast.io/current/comments/fileshare/1234567890123456789/n1o2p3q4r5s6t7u8v9w0x1/" \
-H "Authorization: Bearer {jwt_token}"
Request Example — create
curl -X POST "https://api.fast.io/current/comments/fileshare/1234567890123456789/n1o2p3q4r5s6t7u8v9w0x1/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"body": "Received — thanks for sending this over."}'
The list and create responses use the same envelope and Comment Object schema as the entity-scoped endpoints above. A File Share comment carries the File Share's ID in scope_id, and — for an externally-authored comment — the external_author_* fields identify the author while user_id is null.
Notes
- File Share comments are visible read-only in the owning workspace's node thread (
can_reply/can_edit/can_deletearefalsethere); replies and edits happen through this File Share surface. See the List Comments notes for the one-directional visibility rules. - The invalid-
entity_typeerror message for the whole comment surface isEntity type must be "workspace", "share", or "fileshare".
Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | Entity type must be "workspace", "share", or "fileshare" | Invalid entity type |
1680 (Access Denied) | 401 | Invalid entity or insufficient permissions | Caller cannot access the File Share |
1609 (Not Found) | 404 | Invalid entity or insufficient permissions | File Share or node not found |
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, and File Share.
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) | 406 | Comment body must be between 1 and 8192 characters | Body length out of range |
1605 (Invalid Input) | 406 | 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) | 401 | 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, and threading are immutable here.
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) | 406 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 406 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1680 (Access Denied) | 401 | 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) | 406 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 406 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist |
1605 (Invalid Input) | 406 | Comment is already deleted | Comment was previously deleted |
1680 (Access Denied) | 401 | You do not have permission to delete this comment | Not author or entity owner |
1610 (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 |
| Processing error | Unhandled error while processing this comment |
Request-Level Error Responses
| Error Code | HTTP Status | Message | Cause |
|---|---|---|---|
1605 (Invalid Input) | 406 | Invalid JSON request body | Malformed JSON |
1605 (Invalid Input) | 406 | comment_ids array is required | Missing or non-array field |
1605 (Invalid Input) | 406 | comment_ids array cannot be empty | Empty array |
1605 (Invalid Input) | 406 | 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) | 406 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 406 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist or is deleted |
1680 (Access Denied) | 401 | You do not have permission to react to this comment | User lacks entity access |
1605 (Invalid Input) | 406 | emoji parameter is required | Missing or non-string emoji |
1605 (Invalid Input) | 406 | Invalid emoji character | Does not match Unicode emoji pattern |
1605 (Invalid Input) | 406 | Only single emoji allowed | String exceeds 2 UTF-8 characters |
1610 (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) | 406 | Comment ID is required | Missing comment ID |
1605 (Invalid Input) | 406 | Invalid comment ID format | Malformed opaque ID |
1609 (Not Found) | 404 | Comment not found | Comment does not exist or is deleted |
1680 (Access Denied) | 401 | You do not have permission to react to this comment | User lacks entity access |
1605 (Invalid Input) | 406 | emoji must be a string | Non-string emoji parameter |
1605 (Invalid Input) | 406 | Invalid emoji character | Does not match Unicode emoji pattern |
1610 (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.
Comment Attachments
Attach arbitrary platform objects to a comment to give it context — a file or folder, a sign envelope, a share, a File Share, or a workspace. 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).
- A soft-deleted comment returns
404.
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/envelope/File Share, or an opaque ID for a file/folder node) |
| target_type | string | Coarse object type: node, envelope, share, workspace, or fileshare |
| 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) | 406 | A target_id or non-empty target_ids array is required | No target supplied |
1605 (Invalid Input) | 406 | Invalid attachment target | A target ID could not be decoded |
1605 (Invalid Input) | 406 | Attachment limit reached (maximum 25 per comment) | The new attachments would exceed the cap |
1680 (Access Denied) | 401 | 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.
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 a specific position within a file using the reference object. The type field is required and selects which additional fields are valid.
type values
| type | Applies to | Valid anchor fields |
|---|---|---|
"video" | Video files | timestamp (or timestamp_start + timestamp_end); optional region |
"audio" | Audio files | timestamp (or timestamp_start + timestamp_end) |
"document" | Documents / text | page, text_snippet |
"image" | Images | region |
"general" | Any file | none (a bare {"type": "general"} anchor) |
Anchor fields
| Field | Type | Used with | Description |
|---|---|---|---|
| timestamp | number | video, audio |
Position in seconds (non-negative). Mutually exclusive with the range fields |
| timestamp_start / timestamp_end | number | video, audio |
A time range in seconds (both non-negative, end > start) |
| page | integer | document |
Page number (positive integer) |
| text_snippet | string | document |
The referenced text passage (max 500 characters) |
| region | object | video, image |
Rectangular area with keys x1, y1, x2, y2, each a number 0–100 (percent of width/height), with x2 > x1 and y2 > y1 |
Any unrecognized type is rejected with "Invalid reference type: {value}".
Example — Video timestamp
{
"body": "The transition at this point needs work.",
"reference": {"type": "video", "timestamp": 42.5}
}
Example — Document page
{
"body": "Typo in the second paragraph.",
"reference": {"type": "document", "page": 7}
}
Example — Image region
{
"body": "This area needs higher contrast.",
"reference": {"type": "image", "region": {"x1": 20, "y1": 15, "x2": 55, "y2": 40}}
}
Example — Document text snippet
{
"body": "This sentence should be rephrased.",
"reference": {"type": "document", "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 (406 error).
- Profanity/PII filtering: Detected content is modified in-place before storage; the returned
bodyreflects the post-filter content.