Per-Workspace Dashboard A ranked, paginated feed of actionable cards for each workspace member — mentions, file activity, and pending signatures — with an optional AI overlay.
The Dashboard assembles a single, ranked, paginated list of actionable cards for the calling workspace member. Cards are drawn from deterministic sources (@mentions, file activity, and pending signatures) and ordered by urgency, due date, and type. On workspaces whose plan includes AI features, an AI-generated summary, urgency score, and suggested action may be layered on top of each card, and cross-item synthesis cards may appear at the end of the feed.
The dashboard is per-workspace and per-member. A member sees only the cards relevant to them in that workspace. Dismiss and snooze operations are also per-member and out-of-band — they hide a card from the member's view without changing the underlying signature or activity item.
Endpoint Summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /current/workspace/{workspace_id}/dashboard/ | Get the calling member's dashboard card feed |
| POST | /current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/ | Dismiss or snooze a card |
| DELETE | /current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/ | Undismiss a card (restore it to the feed) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/ | Mint the caller's signing link for a signature card |
Card Schema
Every card in the cards array has the following shape. Fields that are AI-only are null when no AI overlay is available for the workspace.
| Field | Type | AI-only | Description |
|---|---|---|---|
| card_key | string | Stable identifier for this card, formed <lane>:<id>. Used as the path parameter for dismiss/undismiss. URL-encode it when constructing the dismiss URL (it contains :). Examples: sign_envelope:9876543210987654321, comment:5556667778889990001. File-activity cards are coalesced per file and keyed file_activity:{node_id} (one card per file, not per event). | |
| type | string | Card type. One of signature, mention, file_version, file_added, synthesis. See Card Types. | |
| source | string | Which lane produced this card. One of event, signature, ai. Synthesis cards always have source: "ai". | |
| actor | object | Who triggered the card. Shape: {"id": string|null, "display_name": string}. id is null when the actor is the system or is not available. | |
| target | object | What the card points at. Shape: {"kind": string, "id": string|null, "name": string, "deep_link": string|null}. kind identifies the target type (e.g. "file", "sign_envelope"). deep_link is an app-level URL to the item when available. | |
| summary | string | Deterministic, human-readable summary of the card. Always present regardless of AI availability. | |
| summary_segments | array or null | Ordered text/ref segmentation of summary for client-side pillification of named entity references. null when no segments are emitted — render the plain summary string. Each item is {"type":"text","text":...} or {"type":"ref","ref_type":...,"id":...,"label":...,"deep_link":...}; concatenating each text and each ref's label, in order, reproduces summary exactly. ref_type is one of file,folder,user,workspace,share,org,signature. | |
| ai_summary | string or null | Yes | AI-generated narrative for this card. null when no AI overlay is present. When present, max 280 characters. |
| urgency | integer or null | Yes | AI-assigned urgency score in the range 0–100. null when no AI overlay is present. Higher values mean higher urgency. A score of 0 is present-but-not-urgent; null means the AI has not evaluated this card. |
| suggested_action | string or null | Yes | AI-suggested next action for this card. null when no AI overlay is present. When present, max 280 characters. |
| primary_action | object | The card's primary action. Shape: {"kind": string, "method": string, "endpoint": string, "payload_template": object}. kind describes the action type (e.g. "resolve", "sign"). method is the HTTP method ("GET" or "POST"). endpoint is the relative API path to call. payload_template carries any fixed parameters to include in the request body. | |
| ripley_seed | object | Seed for launching a pre-focused Ripley Agent conversation about this card. See Ripley Agent Seed. | |
| blocking | boolean | Whether this card blocks workflow progress for other participants. Blocking cards are ranked first in the feed. A pending signature that gates later signers is blocking; a mention is not. | |
| due_at | string or null | Canonical UTC due timestamp ("YYYY-MM-DD HH:MM:SS UTC"), or null when no due date is set. Cards with an earlier due_at are ranked ahead of undated cards. | |
| created_at | string | Canonical UTC creation timestamp ("YYYY-MM-DD HH:MM:SS UTC"). Always present. | |
| dismissible | boolean | Whether the caller may dismiss or snooze this card. Non-dismissible cards must be acted on (or will naturally disappear once their underlying item resolves). | |
| event_count | integer or null | Number of recent events this card represents when it coalesces several — e.g. the new-version count on a coalesced file_version card (the summary reads “uploaded N new versions of …”). null for cards that represent a single item. | |
| actor_count | integer or null | Number of distinct actors across the coalesced events (e.g. distinct uploaders of a file's recent versions). null when not applicable. |
Card Types & Sources
Card type values
| Value | Description | Typical source |
|---|---|---|
signature | The caller has a pending signature on an envelope in this workspace. | signature |
mention | The caller was @-mentioned in a comment in this workspace. | event |
file_version | Recent new version(s) of a file. File activity is coalesced per file — one card per file, carrying an event_count of new versions (the summary reads “uploaded N new versions of …”). Passive activity: capped at the ~5 most-recently-active files and ranked below actionable cards. | event |
file_added | A file recently added to the workspace (no new versions in window). One card per file. Passive activity (same cap and ranking as file_version). | event |
synthesis | An AI-generated cross-item summary grouping related cards. Always appears last in the feed. source is always "ai". | ai |
Cards are ranked in this priority order: actionable cards first (signatures and mentions always rank above passive file activity, regardless of recency), then within a tier by blocking, then by due_at ascending (nulls last), then by urgency descending (nulls last), then by created_at descending, then by type weight in the order listed above.
Card source values
| Value | Description |
|---|---|
event | Produced from a platform activity event (mention, file upload). |
signature | Produced from a pending sign envelope where the caller is an actionable signer. |
ai | Produced by the AI overlay (synthesis cards only). |
AI Overlay
On workspaces whose plan includes AI features, the dashboard may carry an AI overlay. The overlay is computed asynchronously in the background and merged onto the deterministic card feed on each read.
- Additive only. The AI overlay can enrich existing cards with
ai_summary,urgency, andsuggested_action, and appendsynthesiscards at the end of the feed. It cannot fabricate new non-synthesis cards, change a card's target, or cause cards to appear that do not belong to the calling member. - Graceful degradation. When no overlay is available (AI not entitled on the plan, overlay not yet computed, or overlay stale relative to the current card set), the feed is served with purely deterministic cards.
ai_summary,urgency, andsuggested_actionarenull; no synthesis cards appear. - Compute-on-open. The first time a member opens their dashboard, and periodically when the overlay grows stale, a background refresh is triggered automatically. Rapid successive opens are coalesced — the refresh fires at most once per debounce window.
- Urgency range. When set,
urgencyis an integer in0–100. A present0means evaluated and not urgent;nullmeans not yet evaluated. Higher is more urgent. AI urgency participates in card ranking.
Ripley Agent Seed (ripley_seed)
Every card carries a ripley_seed object that pre-populates a Ripley Agent conversation focused on the card's subject matter. Pass this seed to the AI agent when the member selects a "Ask Ripley" affordance on a card.
Shape
{
"question": "Help me review and act on the signature request \"Q4 Contractor Agreement\".",
"subjects": [
{
"type": "signature",
"id": "5556667778889990001",
"display_text": "Q4 Contractor Agreement"
}
]
}
| Field | Type | Description |
|---|---|---|
| question | string | A first-person, actionable question for the agent to start with. Pre-filled based on the card type and target name. Always present (may be a generic fallback if the target has no name). |
| subjects | array | Typed entity references (pills) the agent conversation should be focused on. May be empty for synthesis cards or cards with no addressable target. Each entry has type, id, and display_text. |
Subject type values
| Value | Entity |
|---|---|
file | A file node |
folder | A folder node |
comment | A comment |
signature | A sign envelope |
share | A share |
user | A user |
workspace | A workspace |
org | An organization |
Pagination
The dashboard uses offset-based pagination. Default page size is 50; maximum is 200.
| Query param | Type | Default | Description |
|---|---|---|---|
| limit | integer | 50 | Number of cards to return (1–200). |
| offset | integer | 0 | Number of cards to skip (0+). |
| refresh | boolean | true | false (or 0) = passive read: never enqueue an AI refresh. A still-compatible stored overlay is returned as usual; one that no longer matches the current card set is withheld and reported stale, as on a normal read. |
Read side effect (billing): by default a read may enqueue an AI refresh of the overlay, billed to the workspace's organization as AI usage, when the plan includes AI features, the token has full access, the stored overlay is absent, older than the freshness window, or no longer matches the current card set, and no refresh was requested in the last couple of minutes. A still-compatible stored overlay is returned while the refresh runs. Pass refresh=false to guarantee a read never triggers that spend.
The response includes a standard pagination envelope (total, limit, offset, has_more) plus dismissed_recent_count — the number of cards the caller has dismissed or snoozed in the trailing 24 hours.
Get Dashboard
/current/workspace/{workspace_id}/dashboard/
Returns the calling workspace member's ranked, paginated dashboard card feed. Includes an AI overlay when the workspace plan supports it.
Auth: JWT required. Workspace membership (View or above) required.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit numeric workspace ID or folder_name |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 50 | Cards per page (1–200) |
| offset | integer | 0 | Cards to skip |
| refresh | boolean | true | false = passive read (never enqueues an AI refresh) |
Example
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/dashboard/?limit=25" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"cards": [
{
"card_key": "sign_envelope:5556667778889990001",
"type": "signature",
"source": "signature",
"actor": {
"id": null,
"display_name": "Alice Johnson"
},
"target": {
"kind": "sign_envelope",
"id": "5556667778889990001",
"name": "Contractor Agreement",
"deep_link": null
},
"summary": "You are a pending signer on Contractor Agreement.",
"summary_segments": null,
"ai_summary": null,
"urgency": null,
"suggested_action": null,
"primary_action": {
"kind": "sign",
"method": "POST",
"endpoint": "/current/workspace/1234567890123456789/sign_envelopes/5556667778889990001/my_sign_link/",
"payload_template": {}
},
"ripley_seed": {
"question": "Help me review and act on the signature request \"Contractor Agreement\".",
"subjects": [
{
"type": "signature",
"id": "5556667778889990001",
"display_text": "Contractor Agreement"
}
]
},
"blocking": true,
"due_at": null,
"created_at": "2026-06-15 14:00:00 UTC",
"dismissible": true,
"event_count": null,
"actor_count": null
}
],
"pagination": {
"total": 7,
"limit": 25,
"offset": 0,
"has_more": false
},
"dismissed_recent_count": 2,
"ai_overlay": {
"generated_at": "2026-09-02 13:05:41 UTC",
"stale": false,
"refresh_requested": false
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| cards | array | Ranked, paginated list of dashboard cards. See Card Schema. |
| pagination.total | integer | Total number of visible (non-dismissed) cards for this member in this workspace. |
| pagination.limit | integer | Page size used. |
| pagination.offset | integer | Offset used. |
| pagination.has_more | boolean | Whether additional cards exist beyond this page. |
| dismissed_recent_count | integer | Number of cards the caller has dismissed or snoozed in the trailing 24 hours. |
| ai_overlay.generated_at | string | null | When the merged AI overlay was generated (Y-m-d H:i:s UTC); null when no compatible overlay was merged (none generated yet, card set no longer matches, or the workspace has no AI features). |
| ai_overlay.stale | boolean | true when no compatible overlay was merged or the merged one is older than the freshness window. |
| ai_overlay.refresh_requested | boolean | true when this read enqueued an AI refresh; always false with refresh=false. |
Error Responses
| Error Code | HTTP Status | Cause |
|---|---|---|
10011 (Authentication Invalid) | 401 | Missing or invalid bearer token |
10545 (Access Denied) | 401 or 403 | 401 — the authenticated caller is not a member of {workspace_id} (or lacks at least PERM_VIEW): “Appropriate access is not granted to this workspace.” 403 — the token is valid, signed and unexpired but its scope does not cover the workspace’s parent org. |
10581 (Not Found) | 404 | {workspace_id} does not exist or is not visible to the caller (the “does not exist” and “exists in a different tenant” cases are indistinguishable by design) |
Dismiss or Snooze a Card
/current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/
Dismiss a card permanently, or snooze it until a future timestamp. A dismissed card is hidden from the caller's feed without changing the underlying item (the signature or activity is unaffected). A snoozed card reappears automatically once the snooze expires, without any cache flush needed.
Auth: JWT required. Workspace membership (View or above) required.
URL-encode
{card_key}. Card keys contain:(e.g.sign_envelope:1234567890123456789) and must be percent-encoded in the URL path:sign_envelope%3A1234567890123456789.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit numeric workspace ID |
| {card_key} | string | Yes | The card_key from the dashboard card. Must be URL-encoded (contains :). |
Request Body (optional, application/json)
| Field | Type | Required | Description |
|---|---|---|---|
| snooze_until | string | Snooze expiry in canonical UTC format: "YYYY-MM-DD HH:MM:SS UTC". Must be in the future. When omitted (or null), the card is permanently dismissed. When provided, the card is hidden until this time and then reappears automatically. |
Examples
# Permanent dismiss
curl -X POST \
"https://api.fast.io/current/workspace/1234567890123456789/dashboard/cards/sign_envelope%3A9876543210987654321/dismiss/" \
-H "Authorization: Bearer {jwt_token}"
# Snooze until tomorrow
curl -X POST \
"https://api.fast.io/current/workspace/1234567890123456789/dashboard/cards/sign_envelope%3A9876543210987654321/dismiss/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"snooze_until": "2026-06-18 09:00:00 UTC"}'
Response (200 OK)
{
"result": true,
"card_dismiss": {
"card_key": "sign_envelope:9876543210987654321",
"dismissed": true,
"snooze_until": null
}
}
// Snooze response
{
"result": true,
"card_dismiss": {
"card_key": "sign_envelope:9876543210987654321",
"dismissed": true,
"snooze_until": "2026-06-18 09:00:00 UTC"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| card_dismiss.card_key | string | The card key that was operated on. |
| card_dismiss.dismissed | boolean | true when the dismiss was persisted. false when the dismiss store is not yet available (the card was not hidden; retry later). |
| card_dismiss.snooze_until | string or null | Snooze expiry timestamp, or null for a permanent dismiss. |
| card_dismiss.note | string | (Only when dismissed: false) A human-readable note explaining why the dismiss did not persist. |
Error Responses
All input-validation failures share the category label (Invalid Input) and HTTP 406; the distinct error.code identifies the exact cause. Auth failures mirror the dashboard read endpoint (10011 / 10545 / 10581) — and as there, 10545 is 401 for a membership failure but 403 when the token’s scope does not cover the workspace’s parent org.
| Error Code | HTTP Status | Cause |
|---|---|---|
128636 (Invalid Input) | 406 | Card key contains non-printable or whitespace characters, or exceeds the length limit |
110707 (Invalid Input) | 406 | Card key is missing from the URI |
137570 (Invalid Input) | 406 | snooze_until is not a valid canonical "YYYY-MM-DD HH:MM:SS UTC" timestamp |
143098 (Invalid Input) | 406 | snooze_until is not in the future |
149351 (Invalid Input) | 406 | Request body is not a valid JSON object when Content-Type: application/json is set |
122641 (Invalid Input) | 406 | snooze_until is present but is not a string |
Undismiss a Card
/current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/
Remove a dismiss or snooze, restoring the card to the caller's feed. This is a best-effort operation: undismissing a card that was never dismissed succeeds silently.
Auth: JWT required. Workspace membership (View or above) required.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit numeric workspace ID |
| {card_key} | string | Yes | URL-encoded card key |
Example
curl -X DELETE \
"https://api.fast.io/current/workspace/1234567890123456789/dashboard/cards/sign_envelope%3A9876543210987654321/dismiss/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK)
{
"result": true,
"card_dismiss": {
"card_key": "sign_envelope:9876543210987654321",
"dismissed": false,
"snooze_until": null
}
}
Error Responses
| Error Code | HTTP Status | Cause |
|---|---|---|
128636 (Invalid Input) | 406 | Card key is invalid (non-printable characters, whitespace, or over length limit) |
110707 (Invalid Input) | 406 | Card key is missing from the URI |
Get Signing Link for a Signature Card
/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/
Mints a usable signing link for the calling workspace member when they are a currently-actionable pending signer on the specified sign envelope. This is the primary action endpoint for dashboard signature cards.
Auth: JWT required. Workspace membership required. A write-capable token is required (read-only scoped tokens cannot mint signing links).
Method note: This is a POST because minting a signing link is a state-changing operation — it reserves a per-user throttle slot for the envelope. An empty request body is expected.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| {workspace_id} | string | Yes | 19-digit numeric workspace ID |
| {envelope_id} | string | Yes | 19-digit numeric sign envelope ID (from the dashboard card's target.id) |
Example
curl -X POST \
"https://api.fast.io/current/workspace/1234567890123456789/sign_envelopes/5556667778889990001/my_sign_link/" \
-H "Authorization: Bearer {jwt_token}"
Response — Actionable (200 OK)
Caller is an active pending signer and their routing slot is open. A signing URL is returned.
{
"result": true,
"sign_url": "https://sign.fast.io/s/eyJhb...",
"recipient_status": "sent",
"auth_method": "email_link",
"routing_order": 1,
"blocked_signers": [],
"is_terminal": false
}
Response — Blocked (200 OK)
Caller's routing slot has not yet been reached (an earlier signer in the routing order has not yet signed). No signing URL is returned. blocked_signers lists the downstream signers still waiting on the caller (those whose routing_order is strictly greater than the caller's).
{
"result": true,
"sign_url": null,
"recipient_status": "pending",
"auth_method": "email_link",
"routing_order": 2,
"blocked_signers": [
{ "display_name": "Alice Johnson", "routing_order": 3 }
],
"is_terminal": false
}
Response — Terminal (200 OK)
The envelope is in a terminal state (completed, declined, voided, expired). Nothing to sign.
{
"result": true,
"sign_url": null,
"recipient_status": null,
"auth_method": null,
"routing_order": null,
"blocked_signers": [],
"is_terminal": true
}
Response — Re-auth Required (200 OK)
The caller already has an in-progress signing session or has already authenticated via OTP. The caller should continue from their existing session rather than starting a new one.
{
"result": true,
"sign_url": null,
"recipient_status": "authenticated",
"auth_method": "email_otp",
"routing_order": 1,
"blocked_signers": [],
"is_terminal": false,
"reauth_required": true
}
Response Fields
| Field | Type | Description |
|---|---|---|
| sign_url | string or null | The signing URL when the caller is actionable. null in all other cases. |
| recipient_status | string or null | The caller's recipient row status on this envelope ("sent", "viewed", "pending", "authenticated", etc.). null for terminal envelopes. |
| auth_method | string or null | Authentication method for the caller's recipient row ("email_link", "email_otp", "sms_otp"). null for terminal envelopes. |
| routing_order | integer or null | The caller's routing order on this envelope. null for terminal envelopes. |
| blocked_signers | array | The downstream signers still waiting on the caller — pending recipients whose routing_order is strictly greater than the caller's (i.e. the signers the caller is holding up, not the signers ahead of the caller). Populated for the actionable and blocked states; empty for terminal and re-auth states. Each entry has display_name (string) and routing_order (integer). |
| is_terminal | boolean | true when the envelope is in a final state and can no longer be acted on. |
| reauth_required | boolean | (Only present when true.) Indicates the caller must continue from an existing signing session rather than starting a new one via a fresh link. |
Error Responses
| Error Code | HTTP Status | Cause |
|---|---|---|
10011 (Authentication Invalid) | 401 | Missing or invalid bearer token |
177577 (Access Denied) | 401 | Signing is not enabled for the caller’s organization (plan-gated; enabled on every plan today) |
181120 (Access Denied) | 401 | Caller is not a signer on this envelope |
104318 (Access Denied) | 401 | Too many sign-link requests for this envelope (per-user rate limit exceeded). Back off and retry. |
10754 (Access Denied) | 403 | Token scope is insufficient — a read-only scoped token cannot mint a signing link (this is a write operation) |
115889 (Invalid Input) | 406 | Invalid envelope ID |
199696 (Not Found) | 404 | Envelope ID is not a sign-envelope-typed ID |
113559 (Not Found) | 404 | Sign envelope not found (or the caller cannot load it) |
124036 (Not Found) | 404 | Sign envelope is no longer available (deleted or closed) |
182106 (Not Found) | 404 | URL {workspace_id} does not match the envelope's parent workspace |