Events & Activity Event search, activity polling, WebSocket realtime.
Events capture every action in the system — file operations, membership changes, comments, AI activity, billing, and more. Use the event search endpoints to query the log, activity polling for efficient change detection, and WebSocket for real-time delivery.
Events Search
Search and filter the event log. Events capture every action in the system — file operations, membership changes, comments, AI activity, billing, and more.
Search Events
/current/events/search/
Search and filter events with comprehensive filtering options. Pages by offset or by an opaque keyset cursor.
Auth: Required (JWT). Subject to global rate limiting; see the rate-limit section in the main reference.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| user_id | string | Conditional | — | Filter by user profile ID. One of user_id, org_id, workspace_id, share_id, or parent_event_id is required. |
| org_id | string | Conditional | — | Filter by organization profile ID |
| workspace_id | string | Conditional | — | Filter by workspace profile ID |
| share_id | string | Conditional | — | Filter by share profile ID |
| parent_event_id | string | Conditional | — | Filter by parent event ID for serial/batch events. Cannot combine with filters other than acknowledged, visibility, limit, offset. |
| event | string | No | — | Filter by specific event name (e.g., workspace_storage_file_added). Max 100 characters. |
| category | string | No | — | Filter by event category. See Event Categories. |
| subcategory | string | No | — | Filter by event subcategory. See Event Subcategories. |
| calling_user_id | string | No | — | Filter by the user who triggered the event (19-digit numeric ID) |
| object_id | string | No | — | Filter by related object ID (file, folder, etc.) |
| acknowledged | string | No | — | Filter by acknowledgment status: "true" or "false" |
| visibility | string | No | All non-internal | "external_audit_log" or "external" |
| created-min | string | No | — | Lower bound for event creation time. Accepts ISO 8601 (e.g., 2025-12-01T06:00:00Z) or YYYY-MM-DD HH:MM:SS |
| created-max | string | No | — | Upper bound for event creation time. Same format as created-min; must not be earlier than created-min |
| limit | integer | No | 100 |
Maximum number of results (1–250) |
| offset | integer | No | 0 |
Number of results to skip for pagination. Ignored in cursor mode; sending a non-zero offset together with cursor is rejected. |
| cursor | string | No | — | Keyset cursor for the next page — pass back the pagination.next_cursor you were given. Cannot be combined with a non-zero offset or with parent_event_id. See "Paging a search" below. |
| output | string | No | — | Select the response shape. Comma-separated tokens (e.g. terse, standard, full). See the "Compact Responses" section below for the three detail levels and the fields returned at each level. |
Profile filter priority: If multiple profile filters are supplied, priority is: user_id > org_id > workspace_id > share_id. Only the highest-priority filter is applied.
Example Request
curl -X GET "https://api.fast.io/current/events/search/?workspace_id=1234567890123456789&category=workspace&subcategory=transfer&limit=50" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"events": [
{
"event_id": "ancou-ywgcx-iff7k-pbijp-l4ysg-n43j",
"category": "workspace",
"sub_category": "transfer",
"visibility": "external",
"permission": "member",
"severity": "medium",
"notification": "affected_user",
"description": "The file 'quarterly_report.pdf' has been added to the workspace 'Engineering' by 'Jane Smith'.",
"event": "workspace_storage_file_added",
"required_params": ["file_added", "workspace"],
"node_type": "file",
"operation_type": "add",
"requires_parent_node": false,
"event_profile": "1234567890123456789",
"calling_user": "9876543210987654321",
"object_id": "2lavr2y6uogubeyvfmqv2ur6k6ezt",
"template": {
"description": "The file '{{#name file_added}}' has been added to the workspace '{{#name workspace}}'{{#if calling_user}} by '{{#fullname calling_user}}'{{/if}}.",
"params": ["2lavr2y6uogubeyvfmqv2ur6k6ezt", "1234567890123456789", "9876543210987654321", "9876543210987654321", null]
},
"actor": {
"user_id": "9876543210987654321",
"kind": "agent",
"agent_name": "Dobby",
"name_source": "api_key_label",
"credential_type": "api_key",
"verified": false
},
"created": "2025-01-20 10:30:45 UTC",
"org_id": "1111111111111111111",
"workspace_id": "1234567890123456789",
"user_id": "9876543210987654321",
"acknowledged": false
}
],
"pagination": {
"has_more": true,
"next_cursor": "{opaque_cursor}",
"page_size": 50
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| events | array | Array of event objects |
| events[].event_id | string | Unique event identifier (OpaqueId, emitted in the hyphenated form) |
| events[].created | string | Event timestamp (Y-m-d H:i:s UTC) |
| events[].acknowledged | boolean | Whether the current user has acknowledged this event |
| events[].event | string | Event name identifier (e.g., workspace_storage_file_added) |
| events[].category | string | Event category name |
| events[].sub_category | string | Event subcategory name |
| events[].object_id | string | Related object OpaqueId (if applicable) |
| events[].calling_user | string | 19-digit numeric ID of the attributed user, when one was recorded |
| events[].actor | object | Who acted: user_id, kind (human, agent, api_key, app, system, unknown), agent_name, name_source, credential_type, verified. Absent on events recorded before attribution existed. See the note below |
| events[].org_id | string | Organization ID context (if applicable) |
| events[].workspace_id | string | Workspace ID context (if applicable) |
| events[].share_id | string | Share ID context (if applicable) |
| events[].user_id | string | The event’s subject user: the user the event names (for example a member added or mentioned), otherwise the owner of the org, workspace, or share the event belongs to. Absent on events recorded without one |
| pagination.has_more | boolean | Whether more matching events exist. The only end-of-data signal |
| pagination.next_cursor | string | null | Pass back as cursor for the next page. null whenever has_more is false, and always null when paging by parent_event_id |
| pagination.page_size | integer | The limit you requested (not the number of events returned) |
Rows also carry the event’s definition and rendering fields: visibility, permission, severity, notification, required_params, event_profile (the profile the event was recorded against), the rendered description, and template (the description with {{...}} tokens plus their positional params). File and folder events add node_type, operation_type and requires_parent_node. A few events promote extra fields to the row (for example policy_changes on org_updated, or the fields listed under Compliance & Audit below). Other data supplied when the event was recorded, such as file names or sizes, is not returned as separate fields; names appear only inside description.
Audit-mode-only fields. When visibility=external_audit_log is requested, every row additionally carries ip (string or null) and country (string or null, ISO-3166 alpha-2, or XX/T1) — the client IP and country the event was recorded under. Both are null for historical rows and for events written without a request (background jobs). These two keys are absent in external or default-mode rows, so ordinary members never see a peer’s IP through this endpoint. The sign-in telemetry fields (method, mfa, new_country, client_id, session_id, token_id, agent_name, device_name, user_agent) are audit-mode only in the same way.
Actor attribution. actor is the same object carried by storage nodes, versions, locks, comments and metadata facts (full field reference: Actor Attribution in the Storage reference). It says who the event is credited to and whether an agent acted for them. verified: true appears only on Fastio’s own built-in agent; every other agent_name is self-declared by the credential’s owner or client, so display it but never treat it as verified identity. The older agent_delegation field is legacy — still returned for compatibility, but prefer actor. Events recorded before attribution existed carry no actor; a client may fall back to agent_delegation on those rows. Events from platform editor and pipeline sessions carry no actor, and no longer carry agent_delegation either.
Paging a search
The pagination block is returned on every response, whether you page by offset or by cursor. It is additive — a client that ignores it and keeps stepping offset keeps working unchanged.
- Stop only when
has_moreisfalse. Never stop on a short or empty page. Events you are not permitted to see are removed after the page is read, so a page can come back with fewer events than you asked for — or with none at all — whilehas_moreistrueandnext_cursoris non-null. That page is not the end of the log; follow the cursor. Usehas_moreinstead of a page-length check, not in addition to one. - Cursor mode is the efficient way to walk a long result set (an audit-log export, for example). Request the first page normally, then send the returned
next_cursorback ascursoron each subsequent request, keeping every other filter identical. - Cursors are opaque and forward-only. They encode the page boundary plus a binding to the requesting user and to the request's filters. They are signed so tampering is detected, but they are not encrypted — do not treat one as secret. Do not build, parse, or modify one — pass it back exactly as received, with the same filters. A cursor is valid only for the same user and the same filter set that produced it; change a filter and start again from page one.
limitis not locked to the cursor. Sending a differentlimiton a later page is honoured.- Ordering is newest-first and identical in both modes, so an offset walk and a cursor walk visit the same events in the same order.
parent_event_idis offset-only. Cursors are not available on that path:next_cursoris alwaysnullthere, andhas_moreis still authoritative, so keep steppingoffset.
Rate limiting: if a search is throttled you get 429, which may carry a Retry-After header giving the number of seconds to wait before retrying — honour it when present. When it's absent, back off using the x-ve-limit-* headers instead. The header is exposed to cross-origin browser clients.
Error Responses
Reading the error tables: the four-digit
16xx/17xxvalues below are HTTP-status classes, noterror.code. Theerror.codea client actually receives is assigned per endpoint, so use the HTTP status as the gate and a documentederror.code— five or six digits, plus the9661-9669family — only as a refinement. A16xxvalue identifies the status class — useful for telling which kind of failure occurred — but comparing one againsterror.codewill never match. Five- and six-digit codes (and the9661-9669family) are realerror.codevalues. If you widen a check from a specific code to a status, widen what you assert with it — a status covers failures the narrower code did not, so a message written for that one code becomes a confident falsehood on the rest.
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | No profile filter or parent_event_id provided |
1605 (Invalid Input) | 406 | created-min is greater than created-max |
1605 (Invalid Input) | 406 | parent_event_id combined with disallowed filters |
1605 (Invalid Input) | 406 | cursor combined with a non-zero offset, or with parent_event_id |
1605 (Invalid Input) | 406 | Invalid pagination cursor — malformed, altered, over-long, or issued for a different user or filter set |
1605 (Invalid Input) | 406 | Invalid datetime format for created-min or created-max |
1605 (Invalid Input) | 406 | A profile filter carries an id of a different type (e.g. a workspace id passed as org_id) |
1680 (Access Denied) | 401 | An audit-log query that includes user_id or is anchored only on parent_event_id (see Notes) — no reason field |
1700 (Access Forbidden) | 403 | The token’s scope does not include the org_id, workspace_id, or share_id you filtered on — no reason field |
1700 (Access Forbidden) | 403 | An audit-log query made with a credential that is not admin-capable (rwa) on the anchor — params.reason:"scope_admin_required" |
1600 (Query Error) | 500 | Internal error during event search |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT token |
1651 (Invalid Request Type) | 405 | Wrong HTTP method (only GET accepted) |
1700 (Access Forbidden) | 403 | An anchored audit-log query's caller is neither admin+ nor an entitled compliance auditor — params.reason:"compliance_access_required" |
Notes: Results may be slightly delayed due to caching. OAuth scoped tokens enforce entity-level access: events are filtered to only entities within the token's scope. Events the user cannot access are automatically excluded from results.
Audit-log queries are gated, in two steps. Requesting
visibility=external_audit_logfirst needs an anchor: anorg_id,workspace_id, orshare_idfilter. An audit-log query that includesuser_id(even together withorg_id/workspace_id/share_id—user_idtakes precedence) or that is anchored only onparent_event_idis1680/401 with noreasonfield — this is not the compliance gate below; retry with an entity-anchored filter and nouser_id. Two shapes fail input validation first (1605/406) and never reach this check:parent_event_idcombined withuser_idor any other profile filter, and a query with no profile filter and noparent_event_id. Given an anchor, the caller must hold admin permission on that profile, or — only when the anchor is anorg_id— hold the org’scompliance_auditorflag (Enterprise only — see Compliance & Audit in the Organizations reference; the auditor flag does not extend to aworkspace_id/share_idanchor, only admin permission does there). That second check's refusal is 403 withparams.reason:"compliance_access_required"— branch onreason, not the code. An auditor sees every org-owned audit row even for a workspace they are not a member of; legal-hold events are the one exception and stay owner/auditor-only.
user_idis refused in audit mode — any audit-log query that includesuser_id, with or without another profile filter, is1680/401 with noreason(see above;user_idwithparent_event_idis the1605/406 input error instead) — usecalling_user_id(the actor) instead. For login history:visibility=external_audit_log&event=user_login&calling_user_id={uid}, notuser_id.
Summarize Events (AI)
/current/events/search/summarize/
Search events and generate an AI-powered natural language summary. Accepts all parameters from /events/search/ plus user_context.
This endpoint does not accept cursor and returns no pagination block.
It is a rollup, not a pager. events and the summary cover up to limit events you can see, taken from the matching activity starting at offset. Events you cannot see are skipped and the scan continues, but it stops after examining 2,000 matching events, so a short (or empty) events array is not proof that nothing else matched. offset skips matching events before visibility is applied, so stepping offset by limit does not walk the log — to read every event, page GET /current/events/search/ with cursor.
Auth: Required (JWT). Subject to global rate limiting; shares the events search rate limit bucket.
Additional Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| user_context | string | No | "" |
Focus guidance for the AI summary (e.g., "Focus on uploads"). Max 64 chars; letters, numbers, spaces, . , ! ? ' - only. |
All other parameters are identical to Search Events.
Example Request
curl -X GET "https://api.fast.io/current/events/search/summarize/?workspace_id=1234567890123456789&user_context=Focus%20on%20file%20uploads&limit=100" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"summary": {
"text": "@[user:9876543210987654321:Jane Smith] uploaded 12 files...",
"metrics": {
"total_events": 50,
"unique_actors": 5,
"date_range": {
"start": "2025-01-01 00:00:00 UTC",
"end": "2025-01-20 10:30:45 UTC"
},
"categories": {
"workspace": 38,
"share": 12
}
}
},
"events": []
}
events carries the same event objects as Search Events (left empty above for brevity).
Response Fields (additional to events search)
| Field | Type | Description |
|---|---|---|
| summary | object|null | AI-generated summary, or null if no events, generation failed, or the billing org’s AI policy denies it (see summary_reason) |
| summary_reason | string | Present only when summary is withheld for a known reason. Currently one value: ai_policy_denied — every organization whose events were candidates for the summary denies the caller’s Ripley Agent access. The response is still 200 with the events array populated; only the summary is withheld. |
| summary.text | string | Natural language summary with @[type:ID:name] mention pills |
| summary.metrics.total_events | integer | Total events summarized (at most limit; not a count of everything that matched) |
| summary.metrics.unique_actors | integer | Distinct users who triggered events |
| summary.metrics.date_range.start | string | Earliest event timestamp (Y-m-d H:i:s UTC) |
| summary.metrics.date_range.end | string | Most recent event timestamp (Y-m-d H:i:s UTC) |
| summary.metrics.categories | object | Map of category names to event counts |
Summary Mention Pill Formats
| Entity | Format | Example |
|---|---|---|
| User | @[user:USER_ID:Display Name] | @[user:9876543210987654321:Jane Smith] |
| File | @[file:FILE_ID:filename.ext] | @[file:ancouywgcxiff7kpbijpl4ysgn43j:report.pdf] |
| Folder | @[folder:FOLDER_ID:foldername] | @[folder:2lavr2y6uogubeyvfmqv2ur6k6ezt:Projects] |
| Workspace | @[workspace:WS_ID:name] | @[workspace:1234567890123456789:Engineering] |
| Share | @[share:SHARE_ID:name] | @[share:5555555555555555555:Client Files] |
Error Responses
All errors from /events/search/ apply, plus:
| Error Code | HTTP Status | Description |
|---|---|---|
1680 (Access Denied) | 401 | The org_id, workspace_id, or share_id filter names a profile you are not a member of |
1609 (Not Found) | 404 | The org_id, workspace_id, or share_id filter names a profile that does not exist |
1605 (Invalid Input) | 406 | The id supplied is not of the type the parameter expects (e.g. a workspace id passed as org_id) |
| (generated per call site) | 503 | access_policy_unavailable — an owning org’s Ripley Agent policy verdict could not be read; retry. |
Notes: Summary generation failures are non-fatal:
summaryisnullbut events are still returned.A
user_idorparent_event_idquery can span more than one organization. Each candidate event’s owning org is asked, independently, whether its AI policy allows the caller Ripley Agent access; events belonging to an org that denies it are left out of what the summarizer reads, even though the returnedeventsarray itself is unaffected. If every organization in the candidate set refuses,summarycomes backnullwithsummary_reason: "ai_policy_denied"— the same shape as the single-org case. Anorg_id/workspace_id/share_id-scoped query only ever touches one org, so this only matters for the two profile-less anchors.You must belong to the profile you filter on. This endpoint consumes AI tokens that are billed to the filtered profile's organization, so
org_id,workspace_id, andshare_idare authorized before any summary is generated: the profile must exist, the id must be of the matching type, and you must hold an active membership on it./events/search/(the same query without the summary) is unchanged.The account billed is the one you filtered on. The summary is charged to the organization that owns the profile the query is scoped to — the same profile that selects which events are searched. A query scoped to yourself (
user_id, or aparent_event_idquery) is charged to your own billing account. Where more than one filter is supplied, the one that selects the events (in the orderuser_id,org_id,workspace_id,share_id) is the one authorized and billed; the others do not select or bill anything, but they are still validated, so a malformed or wrong-type value in any of them fails the request.user_id,org_id,workspace_idandshare_idare type-checked here, as on Search Events: each must carry an id of the kind the parameter names. If the billing organization cannot be resolved, the request still succeeds and returns the events withsummaryset tonull; it is never charged to a different account.The same two-step audit-log gate as
/events/search/applies (see its Notes): an audit-log query that includesuser_id(even together withorg_id/workspace_id/share_id—user_idtakes precedence) or that is anchored only onparent_event_idis1680/401 with noreason;parent_event_idcombined withuser_idor any other profile filter, and a query with no profile filter and noparent_event_id, fail input validation first (1605/406); an anchored query from a caller who is neither admin+ on that profile nor — on anorg_idanchor — an entitled compliance auditor is1700/403compliance_access_required.
Event Details
/current/event/{event_id}/details/
Get full details for a single event.
Auth: Required (JWT). Default rate limiting. No credit consumption.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {event_id} | string | Yes | Alphanumeric OpaqueId of the event |
Example Request
curl -X GET "https://api.fast.io/current/event/ancouywgcxiff7kpbijpl4ysgn43j/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"event": {
"event_id": "ancou-ywgcx-iff7k-pbijp-l4ysg-n43j",
"category": "workspace",
"sub_category": "transfer",
"visibility": "external",
"permission": "member",
"severity": "medium",
"notification": "affected_user",
"description": "The file 'quarterly_report.pdf' has been added to the workspace 'Engineering' by 'Jane Smith'.",
"event": "workspace_storage_file_added",
"required_params": ["file_added", "workspace"],
"node_type": "file",
"operation_type": "add",
"requires_parent_node": false,
"event_profile": "1234567890123456789",
"calling_user": "9876543210987654321",
"object_id": "2lavr2y6uogubeyvfmqv2ur6k6ezt",
"template": {
"description": "The file '{{#name file_added}}' has been added to the workspace '{{#name workspace}}'{{#if calling_user}} by '{{#fullname calling_user}}'{{/if}}.",
"params": ["2lavr2y6uogubeyvfmqv2ur6k6ezt", "1234567890123456789", "9876543210987654321", "9876543210987654321", null]
},
"created": "2025-01-20 10:30:45 UTC",
"org_id": "1111111111111111111",
"workspace_id": "1234567890123456789",
"user_id": "9876543210987654321",
"acknowledged": false
}
}
Access Rules
| Condition | Access |
|---|---|
Event is internal visibility | Always denied |
Event has targeted permission | Granted only to the event’s user_id (the target); denied to everyone else, including the calling_user |
User is the calling_user | Granted |
User is the event’s user_id | Granted |
| User has appropriate profile-level permissions | Granted based on permission level |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Event ID missing, empty, or not a valid OpaqueId |
1609 (Not Found) | 404 | No event exists with the provided ID |
1605 (Invalid Input) | 406 | Event has internal visibility |
1605 (Invalid Input) | 406 | User lacks permission to view the event |
1700 (Access Forbidden) | 403 | The event’s organization blocks your location or network — params.reason:"geo_restricted" |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT token |
1651 (Invalid Request Type) | 405 | Wrong HTTP method (only GET accepted) |
Acknowledge Event
/current/event/{event_id}/ack/
Acknowledge (mark as read) an event for the current user. Idempotent.
Auth: Required (JWT). Default rate limiting.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {event_id} | string | Yes | Alphanumeric OpaqueId of the event to acknowledge |
Example Request
curl -X POST "https://api.fast.io/current/event/ancouywgcxiff7kpbijpl4ysgn43j/ack/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Event ID missing or invalid |
1609 (Not Found) | 404 | Event not found |
1605 (Invalid Input) | 406 | Event has internal visibility |
1605 (Invalid Input) | 406 | User lacks permission to view the event |
1700 (Access Forbidden) | 403 | The event’s organization blocks your location or network — params.reason:"geo_restricted" |
1664 (Datastore Error) | 500 | Failed to persist the acknowledgment |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT token |
Note: Acknowledgment is per-user. Acknowledging for one user does not affect others. Same access rules as event details apply. An entitled compliance auditor who is not an org admin may also acknowledge an
org_security_alertrow (see Security Alerts in the Organizations reference).
Compact Responses (output=)
Every endpoint that returns event objects (search, details) 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 event (cumulative) |
|---|---|
terse | event_id, event, category, object_id, created |
standard | terse + sub_category, calling_user, actor, org_id, workspace_id, user_id, share_id, acknowledged, template (containing description + params), severity, visibility, notification (plus ip / country on audit-log searches) |
full | Every field the event carries: standard + permission, description, required_params, event_profile, node_type / operation_type / requires_parent_node (file and folder events), agent_delegation, and any event-specific promoted fields |
Use terse for activity feed tickers and unread-count polling — it carries the event identity (event_id), name (event), category, target object, and a timestamp (created), which is the minimum a feed row needs to render without a follow-up fetch. Use standard for the most common event list/detail views — it adds subcategory, every profile-link id (calling user, owning org/workspace/share), the acknowledgment flag, the template object (which carries the human-readable description and any template params), and the render-hint enums: severity (drives feed-row color/icon), visibility (distinguishes the Activity, Audit-Log, and internal tabs), and notification (drives bell/notification rendering). Use full (or omit the parameter) for audit-log exports and event schema introspection. 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.
Event Categories
| Category | API Value | Description |
|---|---|---|
| Upload | upload | File upload operations |
| User | user | User account events |
| Organization | org | Organization events |
| Workspace | workspace | Workspace operations — and workspace-scoped file/folder activity. See below |
| Share | share | Share operations — and share-scoped file/folder activity. See below |
| AI | ai | AI/ML operations |
| Invitation | invitation | Invitation events |
email | Email-related events | |
| Billing | billing | Billing events (billing_free_trial_ended). The subscription_* events are category org, sub-category billing |
| Metadata | metadata | Metadata operations |
| Apps | apps | Application/integration events |
| Node | node | AI indexing pipeline only — not file operations. See below |
| Server | server | Server/system-level events |
| Import | import | Import operations |
Where File and Folder Activity Lives
File and folder activity is NOT category node. Despite the name, node covers only the AI indexing pipeline (for example node_ai_summary_created), and most of its events are internal and never returned by the API.
It lives in two categories, and filtering only one of them silently omits the other:
category=workspace — workspace-scoped storage:
workspace_storage_file_added workspace_storage_folder_created
workspace_storage_file_updated workspace_storage_folder_updated
workspace_storage_file_deleted workspace_storage_folder_deleted
workspace_storage_file_purged workspace_storage_folder_purged
workspace_storage_file_moved workspace_storage_folder_moved
workspace_storage_file_restored workspace_storage_folder_restored
workspace_storage_file_copied workspace_storage_folder_copied
workspace_storage_lock_overridden
category=share — the share-scoped counterpart, a full parallel set:
share_storage_file_added share_storage_folder_created
share_storage_file_updated share_storage_folder_updated
share_storage_file_deleted share_storage_folder_deleted
share_storage_file_purged share_storage_folder_purged
share_storage_file_moved share_storage_folder_moved
share_storage_file_restored share_storage_folder_restored
share_storage_file_copied share_storage_folder_copied
share_storage_lock_overridden
A consumer that filters only workspace sees no activity in any share. Query both categories for a complete picture of what happened to files.
calling_user_id vs user_id — Actor vs Subject
These two filters answer different questions and are not interchangeable:
| Filter | Matches On | Answers |
|---|---|---|
| calling_user_id | the user who performed the action | “what did this person do” |
| user_id | the event’s subject user — the user the event names where it names one, otherwise the owner of the org, workspace, or share the event belongs to | “what happened to / about this person, or in what they own” |
calling_user_id matches the attributed actor. Usually that is the person who performed the action. Where a background workflow has a responsible party, it can be that party instead — a file that a scheduled sync pulls in is attributed to the user who connected the synced folder, even though nobody clicked anything at the time, so it appears in the activity feed under their name.
⚠ But an absent calling_user_id does NOT mean nobody was responsible. Attribution is recorded per event type, and several types that a user plainly caused still carry no actor:
- Usually attributed — file and folder activity, including files landed by a scheduled sync, and connecting, updating or disconnecting a synced folder. Treat this as the common case, not a guarantee: the actor is omitted whenever it cannot be resolved — for instance once the connecting member has left the workspace, or when the same action completes from a background job rather than the request that asked for it.
- Never attributed — the lifecycle events for syncs, imports and write-backs (a sync starting, completing or failing; an import job; a write-back push). These carry no
calling_user_idat all, even though a specific user connected the folder or triggered the edit. - Connecting or disconnecting a storage provider is attributed from 2026-08-24, and the two differ:
provider_identity_created— always the person who connected the account, which is always its owner. No admin-on-behalf-of path exists here.provider_identity_revoked— the person who initiated it, which may be a workspace admin acting on another member’s connected account.user_idis the account’s owner;calling_useris whoever acted. Omitted entirely when the platform disconnects the account automatically because the owning member was removed from the workspace.
So calling_user_id answers “what is attributed to this person” — right for an actor-scoped audit view, and wrong for “everything that happened in this scope”, where you should omit it. Do not assume it excludes all background activity, and do not read its absence as “the system did this on its own”. Filter on user_id when you want what happened to a person.
Event Subcategories
| Subcategory | API Value | Description |
|---|---|---|
| Storage | storage | File and folder operations: move, copy, delete, restore, version restore, folder create/update, trash emptied, lock override. Uploads and file updates are transfer |
| Comments | comments | Comment activity |
| Members | members | Membership changes |
| Lifecycle | lifecycle | Create and delete events (plus File Share and signing-envelope lifecycle). Profile updates are settings; archive/unarchive is archive |
| Settings | settings | Configuration changes (user_updated, org_updated, workspace_updated, share_updated) |
| Security | security | Security-related events, including sign-ins (user_login, org_sso_login) and two-factor changes |
| Authentication | authentication | SSO sign-up (user_sso_signup). Sign-ins are security |
| AI | ai | AI processing events |
| Invitations | invitations | Invitation management |
| Billing | billing | Subscription and payment |
| Assets | assets | Asset (avatar, branding) updates |
| Upload | upload | Upload events |
| Transfer | transfer | Files added, updated or transferred into storage (uploads, sync, cross-profile), plus download- and preview-token issuance and ZIP downloads. Ownership changes are ownership_transferred under members. |
| Import/Export | import_export | Import/export operations |
| Quick Share | quickshare | Quick share events |
| Metadata | metadata | Metadata operations |
| API | api | API-related events |
| Archive | archive | Archive operations |
email | Email events | |
| Render | render | Preview/thumbnail rendering events (internal; never returned by the API) |
| Cloud Import | cloud_import | Cloud import operations |
Event Visibility Levels
| Visibility | API Value | Description |
|---|---|---|
| Internal | internal | System events. Never accessible via API. |
| Audit Log | external_audit_log | Audit/compliance events. Targeted permission checks bypassed for admins. |
| External | external | Standard user-facing events. |
Default (no visibility parameter): returns both external_audit_log and external events, excludes internal.
Event Permission Levels
| Permission | Description |
|---|---|
member | Any member of the profile can view |
admin | Only admins of the profile can view (plus the event’s own calling_user and user_id) |
targeted | Only the target user (the event’s user_id) can view — not the calling user |
When querying with visibility=external_audit_log, targeted permission checks are bypassed — but the query itself is gated: it requires an org_id/workspace_id/share_id context filter AND (admin permission on that profile, or — only when the filter is org_id — the org’s compliance_auditor flag) (see the Audit-log note under Search Events).
Event History Retention
Event history is kept for a bounded window that depends on the organization’s plan — higher plans retain history for longer. Events older than the organization’s window are removed automatically, so a search over an older period returns fewer results rather than an error.
Two things follow for integrations:
- Export anything you need to keep. If your compliance process requires a longer archive than your plan retains, pull the events you care about through
/current/events/search/and store them yourself. - An empty result is not proof that nothing happened. A search over a period older than the retention window returns no events because the records are gone, not because there was no activity. Check
created-minagainst your plan’s window before treating a gap as meaningful.
An organization that has held a paid subscription keeps a longer minimum window than its current plan alone would give, so billing and dispute history stays available after a cancellation.
Event Names Reference
Workspace Storage
workspace_storage_file_added— File uploadedworkspace_storage_file_deleted— File trashedworkspace_storage_file_purged— File permanently deleted (single purge; emptying the trash does not emit one per item)workspace_storage_file_moved— File movedworkspace_storage_file_copied— File copiedworkspace_storage_file_updated— File updated (renamed, details edited, or content replaced)workspace_storage_file_restored— File restored from trashworkspace_storage_file_version_restored— File version restoredworkspace_storage_folder_created— Folder createdworkspace_storage_folder_deleted— Folder trashedworkspace_storage_folder_purged— Folder permanently deleted (single purge; emptying the trash does not emit one per item)workspace_storage_folder_moved— Folder movedworkspace_storage_download_token_created— Download token issuedworkspace_storage_zip_downloaded— ZIP download completedworkspace_storage_link_added— Link addedworkspace_storage_lock_overridden— A file's edit lock was taken over by another user with write access. Does not name the displaced holderstorage_direct_read_summary— Enterprise orgs only; audit-log only, not a per-download event. See Compliance & Audit below.
Share Storage
share_storage_file_added— File uploadedshare_storage_file_deleted— File trashedshare_storage_file_purged— File permanently deleted (single purge; emptying the trash does not emit one per item)share_storage_file_moved— File movedshare_storage_file_copied— File copiedshare_storage_file_updated— File updated (renamed, details edited, or content replaced)share_storage_file_restored— File restoredshare_storage_folder_created— Folder createdshare_storage_folder_deleted— Folder trashedshare_storage_folder_purged— Folder permanently deleted (single purge; emptying the trash does not emit one per item)share_storage_folder_moved— Folder movedshare_storage_download_token_created— Download token issuedshare_storage_lock_overridden— A file's edit lock was taken over by another user with write access. Does not name the displaced holdershare_storage_zip_downloaded— ZIP download completed
Comments
comment_created— Comment createdcomment_updated— Comment updatedcomment_deleted— Comment deletedcomment_mentioned— User mentioned in comment (targeted permission)comment_replied— Reply to a commentcomment_reaction— Reaction added
Membership
added_member_to_org/removed_member_from_org— Org membershipadded_member_to_workspace/removed_member_from_workspace— Workspace membershipadded_member_to_share/removed_member_from_share— Share membershipmembership_updated— Permission changes
Workspace Lifecycle
workspace_created/workspace_updated/workspace_deletedworkspace_archived/workspace_unarchived
Share Lifecycle
share_created/share_updated/share_deletedshare_archived/share_unarchivedshare_imported_to_workspace— Share imported into workspaceworkspace_folder_share_created/workspace_folder_share_deleted— A workspace folder was shared as a share, or that folder share was removed; carries both the workspace and the share
File Share Lifecycle
Durable single-file File Share events. Category share, external visibility, member permission. Anchored to the owning workspace; the affected File Share id travels in the event data as file_share_id.
file_share_created— A durable File Share was createdfile_share_updated— A File Share's settings (title / access tier / password) were updatedfile_share_content_updated— The shared file's content was replaced (external edit / write-back)file_share_deleted— A File Share was deletedfile_share_access_granted— A per-user grant (view / download / edit) was added or raisedfile_share_access_revoked— A per-user grant was revoked
Cloud Sync Import Events
Category import, sub-category cloud_import, external visibility. Permission is SPLIT, and the split is the useful part: the three import_source_sync_* events are member, because whether a graft synced is operational status a workspace member can already infer from files appearing. Everything else in this family — including both provider_identity_* events, which carry the connected account’s email address — is admin, because whose cloud account is attached is a disclosure question rather than a status one. These are the connect / configure / sync half of cloud sync: linking a provider account, creating a source against it, and running the jobs that pull provider content in. Anchored to the owning workspace via profile_id.
Provider identity (a connected cloud account):
provider_identity_created— A provider account (Dropbox / Box / OneDrive / Google Drive) finished OAuth connect, or was provisioned directly.identity_emailis the connected account’s own email address, interpolated into the description. From 2026-08-24 this event carriescalling_user. Unlike revocation there is no admin-on-behalf-of path here — a connection is always made by the person who will own it — socalling_useranduser_idare the same person. There is no automatic creation path either, so an absent actor on this event always means it predates that date.provider_identity_revoked— Revocation of a connected provider account was INITIATED — by its owner or a workspace admin, or automatically when the connecting member is removed from the workspace. From 2026-08-24 this event carriescalling_userwhen a person initiated it, and OMITS it for the automatic membership-cleanup path. Because the endpoint admits a workspace admin as well as the owner, the actor may be someone OTHER than the account’s owner —user_idis the owner,calling_useris whoever acted. Nothing in the description names an actor, so read the field, not the text. ⚠ Events recorded BEFORE that date carry no actor regardless of who acted, so on an older row an absent actor means “not recorded”, not “automatic”. Treat it as the START of revocation, not proof it finished. Teardown at the provider is ASYNCHRONOUS and its ordering against this event is not fixed — it may still be pending, or may already have completed, when you see this. The identity’s own status is the authoritative signal; do not infer completion either way from the event.
Import sources (a configured sync connection — one remote folder synced into a workspace):
import_source_created— A source was created against a connected identity.import_source_updated— A source’s settings changed — from the update endpoint, or automatically when its identity is revoked or its connecting member’s workspace membership ends. The two automatic paths differ in actor and you cannot treat them alike: an identity revocation carries the acting user through, while membership cleanup is system-initiated and sends an EMPTYcalling_user. So an empty actor means “the platform did this”, not “nobody did this”.import_source_deleted— A source was deleted.import_source_sync_started— A sync run started for a source.import_source_sync_completed— A sync run finished;file_countandtotal_sizedescribe the source’s post-run state.import_source_sync_failed— A source entered the error state. Usually a sync run failed, but it also fires when the platform’s periodic recovery moves a source that was stranded mid-operation — so it means “this source is now in error”, not necessarily “a run was attempted and failed”.error_messageis filtered before it is stored, but the filter is not a guarantee: text supplied by the cloud provider can reach it verbatim.import_source_disconnected— A source was disconnected. For a source with ≤ 1000 files this fires synchronously from the disconnect endpoint with the requestingcalling_user; for a larger source it fires later from the async disconnect job withcalling_user: ''.
Files arriving from a sync (these are workspace events, not import ones — listed here because cloud sync is what produces them):
workspace_storage_file_sync_added— A file was added to the workspace by a cloud-folder sync.workspace_storage_file_sync_updated— A synced file’s content changed at the provider and the workspace copy was refreshed.
Category workspace, sub-category transfer, external visibility, member permission. A large first sync produces many — roughly one per file. They are attributed to the owner of the connection that grafted the folder, so a synced file reads like an upload by that user rather than appearing authorless.
Treat them as AT MOST ONE BEST-EFFORT emission per detected add or content change — not as a guarantee of one-per-change. Two edges make the stronger reading wrong in opposite directions. Emission is best-effort: if the workspace cannot be resolved at emit time nothing is sent and nothing retries, so a real change can produce ZERO events. And change detection is deliberately conservative: when the stored content fingerprint is missing or unreadable the file is assumed changed, so an event can arrive for a file whose content did not actually change. Do not use these as a ledger of what changed — use them as a prompt to re-read, and let the folder listing be the truth.
There is NO delete counterpart. Nothing is emitted when a sync removes a file that disappeared at the provider. Do not infer from silence that a file still exists — re-read the folder to establish that. This is the single most important line in this section: an absent event here means “no signal”, never “no change”.
Import & discovery jobs (the job record behind a sync run, or behind a pre-source folder listing):
import_job_started— A job started.job_typeisfull_sync,incremental, ordiscovery(listing a connected identity’s shared folders before any source exists — no source row yet, sosource_nameis the provider name rather than a folder name, and there is no pairedimport_source_sync_started). Does not fire fordisconnect-type jobs.import_job_completed— A job completed;files_added/files_updated/files_deletedare per-job counts.import_job_failed— A job failed;error_messagecarries the same caveat asimport_source_sync_failed— filtered, but able to carry provider-supplied text.
source_name is MUTABLE, CLIENT-SETTABLE display text, and it is never scrubbed. It starts as the remote folder or library path inside the user’s connected cloud account (e.g. Imported to Fastio/Images), but the update endpoint lets a caller change it afterwards, so it is untrusted input rather than a reliable provider identifier — do not key anything on it. On discovery events, before a source exists, it carries the provider name instead of a folder name. It is interpolated directly into the description of every event above except the two provider_identity_* events. identity_email on those two is the connected account’s real address. Both can name something outside the workspace’s own content, and neither is redacted before the event is persisted or read. Summarise, don’t relay verbatim — same guidance as the diagnostic-field note under Cloud Sync Write-Back below.
Cloud Sync Write-Back
Category import, sub-category cloud_import, external visibility, admin permission. Emitted when a local change is pushed back to the connected cloud provider. Anchored to the owning workspace.
import_writeback_started— A write-back to the provider was queued for a nodeimport_writeback_completed— The local change was successfully written back to the providerimport_writeback_failed— The write-back failed permanently;error_messagecarries a coarse failure classimport_writeback_conflict— The remote object changed since the local edit began, so the push was not applied
Event data carries profile_id, source_id, node_id and wb_id (plus error_message on _failed). wb_id identifies the write-back itself and is the same across all four events for one push, so it is what correlates a _started with its eventual _completed, _failed or _conflict. node_id cannot do that job: a node edited twice produces two write-backs sharing one node id.
These events do NOT wake a client. They raise no realtime signal — a client must READ the events feed to see them and will not be nudged. This is deliberate: they fire per FILE, so a large sync would otherwise notify once per file.
Treat diagnostic fields as sensitive and untrusted. error_message is a coarse failure class, but diagnostic text in this family can carry provider-supplied content, including the names of files inside a user’s connected cloud account. Summarise it; never relay it verbatim into a chat, ticket, or commit message, and never parse it for control flow.
AI
ai_chat_created/ai_chat_updated/ai_chat_deleted— AI agent conversation lifecycleai_chat_new_message— New message in an AI agent conversationai_chat_published— AI agent conversation published (chat publish is currently disabled platform-wide —capabilities.can_publish_agent_chatisfalse; fires only for chats published before the disable)node_ai_summary_created— AI summary generated for a fileworkspace_ai_share_created— AI share links created in a workspace or share (file_count)workspace_ai_file_downloaded— A file was downloaded through an AI share link (visible to admins only)
Metadata
metadata_kv_update/metadata_kv_delete/metadata_kv_extractmetadata_fact_update— A file’s extracted metadata was updated. Emitted by the machine-extraction write path, so it fires per file across an extraction runmetadata_field_merged— One field was folded into another in a workspace’s field vocabulary
Field vocabulary merges
Category metadata, sub-category metadata, external visibility, member permission. Emitted when a workspace’s field vocabulary changes shape: one field is folded into another, so the folded name stops being its own entry and resolves to the surviving field from then on.
The event data carries workspace plus both NAMES — alias_field_name, the field that was retired, and canonical_field_name, the field it now resolves to. That pair is the point of the event: a consumer holding persisted selections that named the retired field can REWRITE them to the surviving name instead of dropping them, and a cached copy of the vocabulary can be corrected without re-reading the whole listing.
Nothing reaches anyone on its own when this fires — no email, no notification, no webhook, no realtime nudge. The fold is recorded and nothing more, so a consumer that wants to react to one must POLL GET /current/events/search/ for it. Do not wait to be woken; you will not be.
It fires only when a fold actually WRITES. A pre-flight of a merge emits nothing, and neither does a merge that finds the two fields already folded together — so receiving this event always means the vocabulary really changed. The converse does NOT hold. Emission is best-effort and nothing retries, so a real fold can produce no event at all. Do not treat the stream as a ledger of vocabulary changes: treat an event as a prompt to re-read the field vocabulary, and let that listing be the truth.
Templates and saved views (retired — no longer emitted)
Metadata templates have been replaced by the workspace field vocabulary, and per-user saved views have been folded into metadata filters. Both sets of endpoints are gone. These event types are retained only so historical activity stays readable; none of them is ever emitted now, so do not build a subscription or a workflow that waits on one.
metadata_template_update/metadata_template_delete/metadata_template_selectmetadata_view_create/metadata_view_update/metadata_view_delete
Quick Shares (deprecated — see File Share Lifecycle)
QuickShare creation is deprecated in favor of the durable File Share; these events fire only for the draining QuickShare population. New single-file sharing emits the file_share_* events above.
workspace_quickshare_created/workspace_quickshare_updated/workspace_quickshare_deletedworkspace_quickshare_file_downloaded/workspace_quickshare_file_previewed
Invitations
invitation_email_sent/invitation_accepted/invitation_declined
User
user_created/user_updated/user_deleteduser_email_reset/user_asset_updateduser_login— categoryuser, sub-categorysecurity, audit-log visibility only. Written once per Enterprise org the user is a live member of. See Compliance & Audit below.
Organization
org_created/org_updated/org_closed—org_updated'spolicy_changesmap now also carries the three collaboration-policy keys (external_invites_shares/_portals/_workspaces) as{before, after, overrides: {added, removed, changed}}— see Collaboration Policies in the Organizations reference. It further gainsai_agent,ai_intelligence,ai_metadata,ai_summaries,mcp_access(same envelope-diff shape),access_policy({before, after}per role, each reduced to{countries: {mode, count}|null, ips: {count}|null}— no country codes and no IP/CIDR values are carried in the event;overridesstays the usual counts-only{added, removed, changed}diff) andai_workspaces({before: count|null, after: count|null, added, removed}—nullmeans “every workspace”; the diff never lists workspace ids) — see Access Policy (Geo / IP Restrictions) and AI, Intelligence & MCP Access Policy in the Organizations reference. It further gainssecurity_alerts({before, after}, each the parsed envelope ornullfor the defaults) when that setting changes — see Security Alerts in the Organizations reference.
Compliance & Audit
Mostly category org, sub-category security. Exceptions: org_compliance_auditor_changed and org_member_transfer_started / org_member_transfer_completed are sub-category members; user_login and oauth_session_created are category user (sub-category security); storage_direct_read_summary is category workspace, sub-category storage; ownership_transferred is noted on its own entry. All use external_audit_log visibility, visible to org admins and to a member holding the compliance_auditor flag — see Compliance & Audit in the Organizations reference for the endpoints that read and write these.
user_login— a sign-in was completed. Row-root fields:method(password|password_2fa|social:google|social:apple|social:microsoft|sso|oauth_code|oauth_refresh|api_key),mfa(bool —trueonly when Fastio itself verified a second factor; SSO is alwaysfalse),new_country(bool),client_id,session_id,token_id,agent_name,device_name,user_agent(raw, ≤256 chars), plus the audit-modeip/countryevery row carries. Login failures are never recorded. Machine logins (api_key,oauth_refresh) are deduplicated — not every machine-credential request is recorded, so a row's absence does not mean the credential was unused.org_credential_revoked— an admin revoked a member’s API key or OAuth grant. Fields:type,credential_id,reach,target_user_id.org_member_signed_out— an admin force-signed-out a member. Fields:target_user_id,revoked_count,skipped_count.org_audit_exported— an audit-log export ran. Fields:from,to,format,filters,rows,truncated,complete.org_compliance_auditor_changed— the owner granted or revoked a member’s auditor flag. Fields:target_user_id,enabled.legal_hold_created/legal_hold_released— a legal hold was placed or lifted (see Legal Holds in the Organizations reference). Fields:hold_id,target_type(workspace|user),target_id,name— never the hold’sreason. Unlike every other row on this page, these two events are visible only to the org owner or an entitled auditor — never a plain admin — in the audit log, event details, summarize and export alike, and only through a login session or an API key/OAuth token that itself carries an org admin-capable (rwa) scope on that same org — auser:*:rw-scoped key, or a key scoped to a different org, never sees them, even when the key’s owner is the org owner or an auditor. A search filtered to one of these two event types by a caller who does not qualify comes back as an empty page (has_more: false,next_cursor: null), not an error.eventnames are matched exactly — a case, whitespace, or accent variant of a hold-event name, or anyeventvalue that is not itself a plain lowercase name, also returns an empty page rather than falling through to a broader match. They are never forwarded to a configured SIEM stream.org_policy_denied— a request was blocked by the org’s geo/IP access policy or its MCP access policy (see Access Policy (Geo / IP Restrictions) and AI, Intelligence & MCP Access Policy in the Organizations reference). Fields:reason(geo_restricted|mcp_access_denied),rule(country|ip, geo only — absent for an MCP denial),user_id(the blocked user),ip,country,credential_type,route. Rate-limited, so it may not appear for every blocked request.org_access_policy_breakglass— the org owner’s own access-policy break-glass path was actually used to reachorg/{org_id}/details/or.../update/from a location the policy would otherwise block. Fields:user_id(the owner),ip,country,route. Rate-limited.oauth_session_created— an OAuth authorization-code exchange completed (never fires on a token refresh). Written as one row in the user’s own activity (no org — only the user can read it), plus one row per Enterprise org the user is a live member of and the grant’s scopes can reach (a full-account grant or an org, workspace or share wildcard scope reaches every such org). Those org rows appear in that org’s audit-log search and SIEM stream, visible to org admins and auditors. Row-root fields:session_id,client_id,agent_name,mcp(boolean — the grant’s audience is the Fastio MCP), plus the audit-modeip/country.session_id,client_idandagent_nameare audit-only, returned only in an audit-log search, so the user’s own row showsmcpalone. Feeds thecredential_createdsecurity alert, raised on those same orgs withsource_event_idset to that org’s row — see Security Alerts in the Organizations reference.storage_direct_read_summary— a rolled-up count of one member’s authenticated direct/read/downloads on workspace and share storage, aggregated per member per minute (Enterprise orgs only). Fields:user_id,count,window_seconds(always60).countis the number of distinct files read in the minute, not the number of requests — repeated or byte-range reads of the same file within the window count once. Download-token and public-link reads are not counted here — the token mint is already audited by the existing*_download_token_createdevents. Feeds themass_downloadsecurity alert.ownership_transferred— ownership of an org, workspace, or share was transferred to another member (see Transfer Org Ownership in the Organizations reference, Transfer Workspace Ownership in the Workspaces reference, and Transfer Ownership in the Shares reference). Categoryorg|workspace|share, sub-categorymembers. The transferred profile is the row’s ownevent_profile(andorg_id/workspace_id/share_id). Fields:profile_type(org|workspace|share),from_user,to_user,transfer_id(string, only when raised as part of a Bulk Access Transfer below —nullfor a direct ownership transfer).org_member_transfer_started/org_member_transfer_completed— a Bulk Access Transfer (Organizations reference) was queued, and later finished. Fields (both events):transfer_id,counts,complete,offboard_status. On the started event they describe the preview plan (plan counts, whether it was fully enumerated) andoffboard_statusis alwaysnot_requested; the completed event carries the final report counts and offboard outcome.org_audit_stream_updated/org_audit_stream_deleted/org_audit_stream_paused— the org’s SIEM audit stream (see SIEM Audit Stream in the Organizations reference) was created/changed, removed, or auto-paused after sustained delivery failure. Fields:url(host only — never the full URL with path/query),enabled,change(created|updated|enabled|disabled|secret_rotated|deleted|paused_failing),gap_from/gap_to(set only when events aged out while paused).org_security_alert— one of the Security Alerts fired. Severity high, audit log only, no event user — the subject of the alert (if any) rides insubject_user_idonly, so the alert never appears in the subject’s own activity. It also carries nocalling_userand noactor— the platform raises it, not the request that tripped it; render the actor as “System”. Fields:alert_type,subject_user_id(string/null),count(mass alerts only),window_minutes(mass alerts only),source_event_id, plus the audit-modeip/countryof the source event.
Enterprise SSO
Category org, sub-category security, external_audit_log visibility, admin permission — visible only to org admins, in the audit log. Covers an org’s single sign-on configuration and domain claims (GET/POST /current/org/{org_id}/sso/...).
org_sso_updated— The org’s SSO configuration (protocol, mode, provider values, role mapping) was written; also raised when a domain is claimed, a configuration check runs, or the SCIM token is minted or revoked.updateslists the changed field names only (never values);policy_changescarriesmode: {before, after}when the mode moved anddomains: {added, removed}when a domain was claimedorg_sso_deleted— The org’s SSO configuration was removed (verified domains are kept)org_sso_domain_verified— A claimed domain passed DNS verificationorg_sso_domain_unverified— A previously verified domain stopped being verified — either an admin released the claim, or the periodic reverification sweep found the DNS record missing across three consecutive checks. The sweep-triggered case carries nocalling_user— the actor is the platform, not a person; render it as “System”.org_sso_login— A user completed sign-in through the org’s identity provider. Carriescalling_user(the signing-in user)org_sso_certificate_expiring— Defined for a SAML signing-certificate expiry warning, but not currently raised. When it is, it carries nocalling_user(render the actor as “System”). Readcertificate_warningon the SSO configuration for expiry state today
SCIM Provisioning
Category org, sub-category members, external_audit_log visibility, admin permission — visible only to org admins, in the audit log. Raised by an identity provider’s SCIM client (/current/scim/v2/...), never by a person, so none of these three carry a calling_user — render the actor as “Identity provider”.
org_scim_provisioned— A user was added to the org by the identity providerorg_scim_deprovisioned— A user was removed from the org by the identity provider (including the async cascade that follows a SCIM deprovision)org_scim_group_updated— A provisioning group’s membership was created, updated, or deleted by the identity provider
Billing
subscription_created— fires when a new subscription is initiatedsubscription_cancel_scheduled— fires when a customer schedules cancellation; the subscription remains active untilcancel_atsubscription_cancelled— fires when the subscription is actually terminated by the payment provider (atcancel_at)billing_free_trial_ended
Event Search Examples
Recent comments in a workspace
GET /current/events/search/?workspace_id={id}&subcategory=comments
File uploads to a share in a date range
GET /current/events/search/?share_id={id}&event=share_storage_file_added&created-min=2025-12-01T06:00:00Z
Membership changes in an org
GET /current/events/search/?org_id={id}&subcategory=members
AI activity in a workspace
GET /current/events/search/?workspace_id={id}&category=ai
Unacknowledged events for a user
GET /current/events/search/?user_id={id}&acknowledged=false
Audit log events only
GET /current/events/search/?workspace_id={id}&visibility=external_audit_log&limit=100
Child events of a batch operation
GET /current/events/search/?parent_event_id=ancouywgcxiff7kpbijpl4ysgn43j&limit=100
Org Storage Change Feed
A single endpoint that returns every storage change across every workspace and share of an org the caller can read, since a cursor — the org-scale alternative to calling /events/search/ or /activity/poll/ once per workspace and once per share.
Org Storage Changes
/current/org/{org_id}/events/changes/
Return the org's storage changes (file/folder add, update, delete, move, copy, restore, rename, transfer, version restore, trash emptied, links, and cloud-sync file adds/updates) across every workspace and org share the caller can read, in order, after a cursor.
Auth: Required (JWT). Default rate limiting.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {org_id} | string | Yes | 19-digit numeric ID of the organization |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| cursor | string | No | — | Opaque string from a previous response. Position to resume from — omit to bootstrap. |
| limit | integer | No | 250 | Maximum number of changes to return (1–1000) |
Example Request
curl -X GET "https://api.fast.io/current/org/1111111111111111111/events/changes/?limit=500" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"changes": [
{
"event_id": "ancou-ywgcx-iff7k-pbijp-l4ysg-n43j",
"event": "workspace_storage_file_added",
"profile_id": "4829105738291047362",
"profile_type": "workspace",
"object_id": "2ltsu-q4mja-cuv7p-gc5yd-lxnsj-wee4",
"created": "2026-09-25 21:40:11 UTC",
"calling_user_id": "1382049571038475629"
}
],
"cursor": "{opaque_cursor}",
"has_more": false,
"profiles": {
"version": "9b1f0c...",
"items": [
{"id": "4829105738291047362", "type": "workspace"},
{"id": "5510392857104938271", "type": "share"}
]
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| changes | array | Storage changes since cursor, oldest first |
| changes[].event_id | string | Unique event identifier. Dedupe by this — an updated row can reappear with the same event_id; the latest delivery wins |
| changes[].event | string | Event name (e.g. workspace_storage_file_added) — see the Event Names Reference for the full storage set |
| changes[].profile_id | string | 19-digit ID of the workspace or share the change belongs to |
| changes[].profile_type | string | workspace or share |
| changes[].object_id | string | null | Affected object OpaqueId (file, folder, or link). null for a change that is not about a single item (e.g. trash emptied) |
| changes[].created | string | Event timestamp (Y-m-d H:i:s UTC) |
| changes[].calling_user_id | string | 19-digit ID of the attributed actor. Omitted where the caller may not see who acted |
| cursor | string | Opaque, signed, forward-only. Pass back unchanged as cursor on the next call |
| has_more | boolean | true means call again. Can be true even when changes is empty. If has_more is true and the returned cursor is identical to the one you sent, the newest changes are still settling — call again after about 10 seconds, not immediately |
| profiles | object | The caller's current readable set |
| profiles.version | string | Changes when the caller joins/leaves a workspace or share, or their file visibility on a share changes. Re-list profiles.items when it does |
| profiles.items | array | {id, type} for every workspace and share the caller can currently read (a workspace-folder share shadowed by its readable workspace is omitted) |
Bootstrap (no
cursor): returnschanges: [],has_more: false, and a headcursor. Only changes recorded after this call are reported, so listprofiles.itemsin full immediately after taking the head cursor — that listing plus the feed from here forward is the complete picture. The head cursor sits slightly behind the very newest changes (about 10 seconds), so the first follow-up call may return changes your listing already reflects; dedupe byevent_id.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1700 (Access Forbidden) | 403 | Caller has no readable workspace or share in this org |
1605 (Invalid Input) | 406 | Cursor malformed, tampered, or minted for a different caller / org / token scope (for example after the token's scopes changed). Re-bootstrap: take a fresh head cursor, then re-list profiles.items |
1605 (Invalid Input) | 406 | Cursor expired — params.reason: "cursor_expired". Do a full resync: take a fresh head cursor, then re-list profiles.items |
1693 (Temporarily Unavailable) | 503 | Transient read failure. Retry with the same cursor |
Note: Replaces one
/events/search/or/activity/poll/call per workspace and per share for a client that wants "every storage change in this org" — one call and one cursor cover the whole org. A very recent change may be redelivered (the feed briefly holds back the newest rows so a concurrently-committing write is not skipped) — always dedupe byevent_id, keeping the latest delivery. Access is per-caller, not per-org-membership: any user with at least one readable workspace or share in the org is served, whether or not they are an org member; unscoped tokens and workspace-scoped tokens are supported, and a workspace-scoped token sees only its workspaces; share-scoped tokens are supported too — a token scoped to shares is served when at least one of its shares belongs to this org, and sees only its scoped shares' changes (reported as share changes); a token whose shares are all in other orgs gets 403. Coverage gaps to reconcile with a periodic full listing: permanent purges are not reported, a rename arrives as the_updatedevent, some share-only operations have no share-side event, and a very late-committing write can be missed. A change inside a workspace folder share is reported once — as the workspace's change to a caller who can read the workspace, and as the share's change to a caller who can only read the share. See the org channelstoragefield under WebSocket (Real-Time) for the realtime nudge that tells you when to call this endpoint.
Activity Polling
Long-poll endpoints for efficient change detection. The server holds the connection open and returns immediately when something changes, avoiding expensive resource polling.
Poll User Activity
/current/activity/poll/
Poll for activity updates on the current user's profile.
Auth: Required (JWT). Subject to global rate limiting; see the rate-limit section in the main reference. No credit consumption.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| wait | integer | No | 0 |
Long-poll timeout in seconds (0–95). Server holds connection open until update or timeout. |
| lastactivity | string | No | Current time | Only return activity newer than this timestamp. Micro-precision datetime (e.g., 2025-01-20 10:30:45.123456 UTC). |
| updated | any | No | — | Any non-empty value other than 0 enables it: only return activity fields updated since lastactivity |
| fields | string | No | All fields | Comma-delimited activity field names to check. Max 30 fields. Supports ID qualifier via colon (e.g., storage:12345). |
Example Request
curl -X GET "https://api.fast.io/current/activity/poll/?wait=30&lastactivity=2025-01-20%2010:30:45.123456" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK — activity found)
{
"result": true,
"results": 3,
"activity": {
"storage": "2025-01-20 10:30:45.123456 UTC",
"members": "2025-01-20 09:15:22.654321 UTC",
"settings": "2025-01-19 14:00:00.000000 UTC"
},
"lastactivity": "2025-01-20 10:30:45.123456 UTC"
}
Response (200 OK — no activity)
{
"result": true,
"results": 0,
"activity": []
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| results | integer | Number of activity fields returned |
| activity | object or array | Map of activity field names to micro-precision UTC timestamps. When results is 0 this is an empty array [], not {}. |
| lastactivity | string | Most recent timestamp; pass as lastactivity in next poll. Omitted when results is 0. |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Invalid profile ID format |
1605 (Invalid Input) | 406 | User lacks permissions for the specified profile |
1605 (Invalid Input) | 406 | Invalid field names or more than 30 fields |
1665 (Object Init Failed) | 500 | Internal error |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT token |
Poll Profile Activity
/current/activity/poll/{profile_id}/
Poll for activity updates on a specific workspace, share, org, File Share, or Sign Envelope.
Auth: Required (JWT). Same rate limits and parameters as Poll User Activity.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {profile_id} | string | Yes | Profile ID to subscribe to (org, workspace, share, File Share, or Sign Envelope) — a 19-digit numeric profile ID; a File Share may also be addressed by its opaque id_alt. For upload progress, use your user ID. |
Access Requirements
| Profile Type | Permission Required |
|---|---|
| User (self) | Authenticated |
| Organization | View permission on the org |
| Workspace | View permission on the workspace |
| Share | Permission to view the share's details |
| File Share | The same access decision as the File Share's public read surface (access tier + password + named grant). A signed-in recipient granted view/download/edit receives a recipient-scoped feed (comment activity plus a bare content/lifecycle nudge — never the owner's internal activity); an anonymous anyone-with-link visitor cannot poll. |
| Sign Envelope | The e-sign creator-side realtime channel. Requires view access to the sign envelope. |
Example Request
curl -X GET "https://api.fast.io/current/activity/poll/1234567890123456789/?wait=30&fields=storage,members&updated=1&lastactivity=2025-01-20%2010:30:45.123456" \
-H "Authorization: Bearer {jwt_token}"
Response format is identical to Poll User Activity.
Org access/AI policy. For an Org, Workspace, or Share profile owned by an Enterprise org with a restriction configured, a blocked caller gets 403 geo_restricted and an MCP-classified caller blocked by mcp_access gets 403 mcp_access_denied, ahead of the usual profile-lookup error; a policy verdict that cannot be read is 503 access_policy_unavailable. See Access Policy (Geo / IP Restrictions) and AI, Intelligence & MCP Access Policy in the Organizations reference.
Polling Workflow
- Make initial poll request (no
lastactivityparameter) - Receive response with
activityfields andlastactivitytimestamp - Process changes by fetching updated resources based on activity field names
- Make next poll with
lastactivityfrom previous response - Repeat — server returns immediately on change, or after
waitseconds timeout
Activity Key Patterns
| Key Pattern | What Changed |
|---|---|
storage:{fileId} | File added, updated, or removed |
preview:{fileId} | File preview/thumbnail is ready |
metadata:{fileId} | File's extracted metadata fields were written (use to refresh a single row during a template extraction) |
ai_chat:{chatId} | AI chat message updated |
comments:{nodeId} | Comment added or updated |
membership | A member was added, removed, or had their role changed |
Anti-Patterns
- Do NOT loop on resource detail endpoints to wait for previews or processing.
- Instead, poll on the workspace/share and watch for the relevant activity key.
- For uploads, use your user ID as the
profile_idsince upload progress is tied to the user, not a workspace.
WebSocket (Real-Time)
Optional real-time delivery (~300ms latency vs ~1s for polling). Sends both activity messages (field names for change detection) and enriched event messages (full event details) via WebSocket. Backwards compatible — existing clients continue to work without changes.
WebSocket Auth
/current/websocket/auth/{profile_id}
Generate a WebSocket authentication JWT for a specific profile. User, organization, and workspace tokens are valid for 1 hour, Sign Envelope tokens for 24 hours. Share and File Share tokens use a shorter TTL (30 minutes) — always check the expires_in field on every response and refresh before it expires. A File Share realtime channel is gated by the same access decision as its public read surface (access tier + password + named grant), the same requirement as its activity poll. A signed-in recipient granted view/download/edit can mint a token and receives a recipient-scoped feed — comment activity plus a bare content/lifecycle nudge, never the owner's internal activity; an anonymous anyone-with-link visitor cannot mint a realtime token.
Auth: Required (JWT). No credit consumption.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {profile_id} | string | Yes | ID of the user, org, workspace, share, file share, or sign envelope to subscribe to — a 19-digit numeric ID; a File Share may also be addressed by its opaque id_alt |
Example Request
curl -X GET "https://api.fast.io/current/websocket/auth/1234567890123456789" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"expires_in": 3600,
"auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| expires_in | integer | Effective token lifetime in seconds (3600 = 1 hour for user/org/workspace; 1800 = 30 minutes for share and file-share; 86400 = 24 hours for sign envelope) |
| auth_token | string | Signed JWT with websocket scope, bound to the requested profile |
Access Requirements
| Profile Type | Permission Required |
|---|---|
| User (self) | None beyond authentication |
| Organization | View permission on the org |
| Workspace | View permission on the workspace |
| Share | Permission to view the share's details |
| File Share | The same access decision as the File Share's public read surface (access tier + password + named grant), the same gate as its activity poll. A signed-in view/download/edit recipient mints a recipient-scoped token (comment activity plus a bare content/lifecycle nudge only); an anonymous anyone-with-link visitor cannot mint one. |
| Sign Envelope | View access to the sign envelope (the e-sign creator-side realtime channel) |
Token Lifetime
The default expires_in for User, Organization, and Workspace tokens is 3600 (1 hour). Share and File Share tokens are shorter-lived (1800 seconds / 30 minutes) — always inspect the expires_in field on the response and refresh the token before it expires. These channels use a short TTL because the recipient's standing can change during a session, and the token freezes that standing at mint time: a share guest can be removed or have their access level downgraded, and a File Share recipient's grant or the file's access tier/password can change. The short TTL bounds how long an open connection can keep delivering events under a now-stale authorization; on reconnect the freshly minted token reflects the current standing. User, Organization, and Workspace tokens are bounded to an hour for the same reason: once a sign-in session ends (sign out, sign out everywhere, or a password change), requests made with it can no longer mint tokens, so a connection opened through it ends shortly after its token expires. When a user, organization, workspace, share or file-share token expires the server closes the open connection with a denied frame; mint a fresh token and reconnect.
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | No profile ID provided or invalid format |
1605 (Invalid Input) | 406 | Profile type unsupported, not found, or user lacks permissions |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT, or internal JWT generation failure |
| (generated per call site) | 403 | geo_restricted — the Org/Workspace/Share profile is owned by an Enterprise org whose access policy blocks the caller’s location or network. See Access Policy (Geo / IP Restrictions) in the Organizations reference. |
| (generated per call site) | 403 | mcp_access_denied — an MCP-classified caller is blocked by the org’s mcp_access policy. See AI, Intelligence & MCP Access Policy in the Organizations reference. |
| (generated per call site) | 503 | access_policy_unavailable — the access-policy verdict could not be read; retry. |
WebSocket Connection
Connect to: wss://{host}/api/websocket/?{auth_token}
Where {auth_token} is the JWT returned from the auth endpoint above — it is the entire query string (no token= key, no other query parameters).
Message Types
The server pushes two types of JSON messages:
activity Messages
Indicate which resource categories changed. Use these to know what to re-fetch:
{
"response": "activity",
"activity": ["storage:2abc...", "preview:2abc..."]
}
The activity array contains the same activity key patterns as the polling endpoint.
Org Channel storage Field
On an organization channel only, a bare storage key (no :{id} suffix) is pushed at most about once a second per org whenever a storage change occurs in any workspace or share of the org:
{
"response": "activity",
"activity": ["storage"]
}
It is deliberately content-free — the org channel reaches every org member, including members who cannot read the workspace or share that actually changed, so it never names the change. On receipt, call GET /current/org/{org_id}/events/changes/ to learn what happened, and call it again roughly a second later to catch anything that committed just after the ping (there is no trailing signal). Keep a slow periodic call to the same endpoint as a safety net alongside the org channel.
event Messages
Sent alongside activity messages when structured event data is available. Provide full event details for immediate UI updates without a follow-up API call:
{
"result": true,
"response": "event",
"time": "2026-03-22 14:30:45.1234",
"timestamp": "1711123456.1234",
"event": "workspace_storage_file_added",
"category": "workspace",
"subcategory": "storage",
"object_id": "23s5ktto3hoomtz3fbgrmhurl2mi6",
"calling_user_id": "1234567890123456789",
"activity_field": "storage",
"data": {
"name": "report.pdf",
"parent_node_id": "xyz789...",
"size": 1048576
}
}
event Message Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | Always true for event messages |
| response | string | Always "event" for this message type |
| time | string | Server send time (YYYY-MM-DD HH:MM:SS.<fraction> with no UTC suffix and a variable number of fraction digits, e.g. "2026-03-22 14:30:45.1234"). Added when the message is dispatched. |
| timestamp | string | Event trigger time as a microtime float string (e.g., "1711123456.1234"). |
| event | string | Event name identifier (e.g., workspace_storage_file_added) |
| category | string | Event category (e.g., workspace, share) |
| subcategory | string | Event subcategory (e.g., storage, members) |
| object_id | string | Affected object OpaqueId (file, folder, etc.), in its unhyphenated form; empty string when the event has none |
| calling_user_id | string | 19-digit numeric ID of the user who triggered the event; empty string when none was recorded |
| activity_field | string | Corresponding activity field name (e.g., storage) |
| data | object | Event-specific details; shape varies by event type |
Backwards Compatibility
activity messages are sent for every change a recipient is permitted to observe. event messages are supplementary — sent alongside activity messages when structured event data is available. Existing clients need no changes. Event payloads are kept under ~4KB.
Permission gating: Enriched event messages are only sent for member-level events. Admin and targeted events receive only the activity message.
Per-recipient scoping (share channels): On a share channel, each outgoing activity/event frame is scoped to the receiving guest's share access before it is delivered. A guest receives only the changes their access level permits them to observe — frames they may not see are suppressed, and content detail (file names, node identifiers, enriched data) is collapsed to a bare category or dropped for guests with restricted file visibility. Members and all non-share channels (user / organization / workspace) are unaffected and receive the full frame. Do not assume a share guest will observe every change on the channel, or that a delivered activity frame will always carry an enriched event companion.
Fallback
If the WebSocket connection drops, fall back to long-polling (GET /current/activity/poll/{profile_id}/). Activity polling returns field names and timestamps. To retrieve full event details after reconnection, use GET /current/events/search/ with a created-min filter.
Realtime Auth (Collaborative Rooms)
Separate from WebSocket activity channels, these endpoints provide authentication for collaborative editing rooms.
Generate Realtime Token
/current/realtime/auth/{room_id}
Generate a realtime JWT for a workspace or share collaborative room. Tokens are valid for 24 hours.
Auth: Required (JWT). Subject to global rate limiting; see the rate-limit section in the main reference. No credit consumption.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {room_id} | string | Yes | 19-digit numeric ID of the workspace or share to join |
Example Request
curl -X GET "https://api.fast.io/current/realtime/auth/1234567890123456789" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"expires_in": 86400,
"auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Access Requirements
| Profile Type | Permission Required |
|---|---|
| Workspace | At least View permission |
| Share | Permission to view the share's details + multiplayer enabled |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Missing room ID |
1605 (Invalid Input) | 406 | Room ID is not numeric |
1605 (Invalid Input) | 406 | Room ID does not correspond to a workspace or share |
1680 (Access Denied) | 401 | User lacks permissions on the room |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT, or internal JWT generation failure |
Note: Only workspace and share profile types are accepted as room IDs.
Generate Realtime Note Token
/current/realtime/note-auth/{profile_id}/{note_id}
Generate a realtime token for collaborative editing of a single Note. The token is bound to the calling user, the note's workspace, and the note itself, and carries the caller's edit/view standing for the note. Tokens are valid for 900 seconds (15 minutes); refresh before expiry by calling the endpoint again, which re-resolves your current standing on the note.
Auth: Required (JWT). Subject to global rate limiting; see the rate-limit section in the main reference. No credit consumption.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {profile_id} | string | Yes | 19-digit numeric ID of the workspace the note lives in |
| {note_id} | string | Yes | ID of the Note to edit |
Example Request
curl -X GET "https://api.fast.io/current/realtime/note-auth/{profile_id}/{note_id}" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"expires_in": 900,
"auth_token": "{realtime_note_token}"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| expires_in | integer | Token lifetime in seconds (900 = 15 minutes) |
| auth_token | string | Signed realtime-note token, bound to the user, workspace, and note |
Access Requirements
The token's granted capabilities are frozen at mint time from your current permission on the workspace:
| Workspace Permission | Granted Capability |
|---|---|
| Edit (or higher) | Read and edit the note |
| View | Read the note only |
| Below View | Denied |
Because the capabilities are frozen for the token's lifetime, a permission change made after a token is issued takes effect on the next token refresh (at most ~15 minutes later).
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1605 (Invalid Input) | 406 | Missing or invalid note ID, or the node is not a note |
1609 (Not Found) | 404 | No such note exists, or the note is in the trash |
1680 (Access Denied) | 401 | You lack permission on the workspace |
1650 (Authentication Invalid) | 401 | Missing or invalid JWT, or internal token generation failure |
Validate Realtime Token
/current/realtime/auth/validate/
Validate a realtime JWT and extract the room ID. Intended for backend services to verify tokens.
Auth: Bearer token in Authorization header (the realtime JWT to validate, not a user JWT).
Example Request
curl -X GET "https://api.fast.io/current/realtime/auth/validate/" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"result": true,
"room_id": "1234567890123456789"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| room_id | string | The workspace or share profile ID the token is bound to |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1650 (Authentication Invalid) | 401 | Missing Authorization header |
1605 (Invalid Input) | 406 | Malformed Authorization header |
1605 (Invalid Input) | 406 | Authorization header does not use Bearer scheme |
1605 (Invalid Input) | 406 | Bearer keyword present but no token follows |
1605 (Invalid Input) | 406 | Token is not valid JWT format |
1680 (Access Denied) | 401 | Token signature verification or expiration check failed |
1610 (Internal Error) | 500 | Token payload is malformed or missing required fields |
1605 (Invalid Input) | 406 | Token scope is not realtime |
Notes: Only validates tokens with
realtimescope. WebSocket-scoped tokens are rejected. Validation is performed using the JWT alone.
Validate Realtime Note Token
/current/realtime/note-auth/validate/
Validate a realtime-note token (minted by GET /current/realtime/note-auth/{profile_id}/{note_id}) and read back the note, workspace, and permission it is bound to. Intended for the collaborative-editing backend to confirm a token on connect.
Auth: Bearer token in Authorization header (the realtime-note token to validate, not a user JWT).
Example Request
curl -X GET "https://api.fast.io/current/realtime/note-auth/validate/" \
-H "Authorization: Bearer {realtime_note_token}"
Response (200 OK)
{
"result": true,
"profile": "1234567890123456789",
"node": "2ik5q-a43cm-uixi2-van5r-3eolo-7mue",
"perm": "edit",
"file_share_id": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
| result | boolean | true on success |
| profile | string | The workspace profile ID the token is bound to |
| node | string | The Note node ID the token is bound to |
| perm | string | The frozen permission: "edit" (read and edit) or "view" (read only) |
| file_share_id | string|null | The File Share ID the token was minted for when it is a File Share-surface note token; null for a workspace-native token. The realtime backend uses this to route note reads/saves to the File Share-specific endpoints rather than the workspace-native ones. |
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
1650 (Authentication Invalid) | 401 | Missing Authorization header, or the token is invalid, expired, wrong-scope, wrong-audience, or malformed |
↑ Back to topNotes: Only validates tokens with the
realtime-notescope; any other token is rejected. Validation is performed using the token alone — signature, expiry, scope, audience, and the perm/capability binding are all checked.