Core Concepts
Understand the core concepts and architecture of Surflet
Page
A Page is the fundamental delivery unit in Surflet. Each Page contains:
- title / summary / priority — top-level metadata fields
- Blocks — content units (key-value pairs, tables, code, charts, etc.)
- Actions — operations users can perform (approve, reject, etc.)
- Access — access control configuration
- Notify — distribution channel configuration
Lifecycle
draft ──publish──→ active ──┬──(timeout)──→ expired
├──(complete)──→ completed
└──(revoke)───→ revoked
any state ────→ archived
| Status | Description | Interactable |
|---|---|---|
draft | Draft, not yet published | Editable |
active | Published and accessible | Users can execute Actions |
expired | Past its expiry time | Read-only |
completed | All actions completed | Read-only |
revoked | Revoked by the publisher | Not accessible |
archived | Archived | Read-only |
Page Types
| Type | page_type | Use Cases |
|---|---|---|
| Briefing | briefing | Research summaries, meeting prep, daily reports |
| Approval | approval | Refund approvals, purchase approvals, access requests |
| Review | review | Code diffs, document edits, proposal comparisons |
| Form | form | Collecting user input, surveys |
| Dashboard | dashboard | Data visualization, metrics overview |
| Custom | custom | Use a custom template |
Payload Structure
{
"version": "1.0",
"page_type": "approval",
"template": "auto",
"title": "...",
"summary": "...",
"access": { "mode": "authenticated", "allowed_identities": ["..."] },
"blocks": ["..."],
"actions": ["..."],
"notify": { "channels": ["..."] },
"on_action": { "default_url": "https://..." },
"approval_chain": { "mode": "sequential", "steps": ["..."] }
}
Block
A Block is the content unit of a Page. There are 25+ block types:
| Type | Purpose | Examples |
|---|---|---|
key_value | Key-value display | Ticket info, user profiles |
text | Markdown rich text | Analysis reports, documentation |
table | Data tables | Order lists, comparison data |
callout | Callout / alert boxes | AI suggestions, risk warnings |
code | Code blocks | Logs, configs, scripts |
timeline | Event timelines | Event history, process flows |
chart | Charts | Trend charts, distributions (bar/line/pie/area/scatter/radar) |
diff | Diff views | Code changes, text edits |
gallery | Image galleries | Screenshots, attachments, photos |
form | Interactive forms | Data collection |
embed | Embedded content | iframes, PDFs, videos |
divider | Dividers | Visual separation |
metric | Metric cards | KPIs, statistics |
buttons | Button groups | Quick actions |
logs | Log streams | Real-time logs, build output |
progress | Progress bars | Task progress, completion rates |
stepper | Step indicators | Multi-step processes |
image | Images | Single image display |
countdown | Countdown timers | Deadlines, time-limited events |
comparison | Comparison cards | Option comparisons, A/B tests |
kanban | Kanban boards | Task management, status boards |
tree | Tree structures | File trees, org charts |
gauge | Gauges | Health scores, utilization |
sparkline | Sparklines | Inline trend charts |
funnel | Funnel charts | Conversion analysis |
thread | Comment threads | Discussion threads, chat logs |
file | File attachments | Download links, document attachments |
heatmap | Heatmaps | Calibration data, correlation analysis, activity heatmaps |
See the Block Types Reference for details.
Block-Level Features
Collapsible
Add collapsible: true to any block to make it collapsible. Set default_open: false to collapse it by default:
{ "type": "table", "title": "Raw Data", "collapsible": true, "default_open": false, "data": {...} }
Conditional Visibility
Use visible_if to show or hide a block based on its own data fields:
{ "type": "callout", "data": { "content": "Profitable!", "style": "success", "pnl": 100 },
"visible_if": { "field": "pnl", "operator": "gt", "value": 0 } }
Operators: gt (greater than), lt (less than), eq (equal), ne (not equal), exists
Page Features
Auto Refresh
Add auto_refresh_seconds to reload the page on a timer:
{ "auto_refresh_seconds": 3600, "blocks": [...] }
Theme Customization
Use the theme object to set dark mode and custom colors:
{ "theme": { "mode": "dark", "accent": "#8b5cf6" }, "blocks": [...] }
| Field | Description |
|---|---|
mode | "dark" or "light" (default) |
accent | Primary accent color (default #3b82f6) |
bg / text / border | Optional custom colors |
PDF Export
All pages have a PDF button in the top-right corner. Clicking it invokes the browser's print dialog. Print styles are optimized (action bars, thread inputs, etc. are hidden).
Action
An Action defines an operation button users can execute on a page. Each Action includes:
action_id— unique identifier, used in callbackslabel— display textstyle— button style (primary/secondary/danger)requires_comment— whether the user must provide a reasonconfirm_message— confirmation dialog text
Approval Chain
Three modes are supported:
- Sequential — step-by-step approval; the next step begins only after the previous one completes
- Parallel — all steps run simultaneously; the chain completes when all steps finish
- Conditional — routes to different steps based on a field value
Decision policies:
any— any one approver's decision passes the stepunanimous— all approvers must agreethreshold— passes when a minimum number of approvals is reachedweighted— weighted voting
See the Approval Workflows guide.
Access Control
Six modes control who can view and interact with a page:
| Mode | Description |
|---|---|
public | Anyone with the link can access |
authenticated | Only specified identities can access |
one_time | Single-use link, self-destructs after viewing |
view_limited | Limited number of views |
time_limited | Expires at a specified time |
password | Password-protected |
See the Access Control guide.
Callbacks & Events
When a user takes an action on a page, Surflet delivers an event to your registered URL. Supported event types:
action.executed— user clicked an action buttonapproval.step_completed— an approval step completedapproval.chain_completed— the entire approval chain completedpage.expired— the page expiredpage.viewed— the page was viewed
See the Callbacks & Events guide.
Streaming Publish (Progressive Updates)
An agent can publish an initial page and then progressively append new content blocks as analysis proceeds. This pattern works well for long-running tasks — users see the page immediately, and content updates in real time as the agent works.
Workflow:
POST /v1/publish— create the initial page (can include a "loading..." indicator)PATCH /v1/pages/:id/blocks— append new blocks (can be called multiple times)- The client receives
blocks.appendedevents in real time via SSE - The VersionBanner component automatically prompts users to view updates
Each append operation increments the page version, preserving a full change history.
See the Streaming Publish guide.
Action Groups (Mutually Exclusive Actions)
Actions can be grouped using the group field. Actions in the same group are mutually exclusive — the user can only select one. This is common in approval scenarios:
{
"actions": [
{"id": "act_approve", "label": "Approve", "style": "primary", "group": "decision"},
{"id": "act_partial", "label": "Partial Approve", "style": "secondary", "group": "decision"},
{"id": "act_reject", "label": "Reject", "style": "danger", "group": "decision"}
]
}
In this example, all three buttons belong to the decision group, so the user can only choose one. After clicking, the remaining buttons become disabled.
Actions not assigned to any group can be executed independently without mutual exclusivity.
Agent Quality Score
Surflet calculates a comprehensive quality score (0–100) for your agent based on decision data from the most recent 100 pages.
Score formula:
score = acceptance_rate * 70% + decision_speed * 30%
- Acceptance Rate: the ratio of accepted (completed) to rejected (revoked) pages. A high acceptance rate indicates strong recommendation and analysis quality.
- Decision Speed: the average time from publish to completion. Fast decisions indicate that pages are well-structured with sufficient information.
Retrieve the score via GET /v1/quality-score. See Analytics & Quality Score.
Page Templates
Surflet provides 10 preset industry templates. Agents pass variables to generate complete structured pages. Templates include sensible block combinations, layouts, and action buttons:
| Template | Use Case |
|---|---|
refund_approval | Refund approval |
deploy_approval | Deployment approval |
daily_briefing | Daily briefing |
incident_report | Incident report |
expense_approval | Expense approval |
access_request | Access request |
customer_survey | Customer survey |
code_review | Code review |
contract_review | Contract review |
onboarding_checklist | Employee onboarding checklist |
List templates via GET /v1/templates, publish via POST /v1/publish/template/:name. See the Templates guide.
Publish Validation & Incremental Fixes
When publishing, the API validates the data format of each block. If issues are found (missing required fields, incorrect field names, etc.), the page still publishes successfully, but the response includes a warnings array:
{
"pageId": "page_xxx",
"pageUrl": "https://...",
"warnings": [
"Block 'b1' (callout): use 'content' not 'message'"
]
}
Agents can incrementally fix individual blocks based on warnings without republishing the entire page:
PATCH /v1/pages/:id/blocks/:blockId
{ "data": { "content": "Corrected content" } }
This design lets agents iterate quickly: publish an imperfect version first (users can already see most of the content), then fix problematic blocks incrementally.
Protocol Compatibility
In addition to the native blocks format, Surflet supports two external protocol formats:
- A2UI (Google):
POST /v1/publish/a2ui— accepts A2UI v0.9 component lists and automatically translates them to Surflet blocks - AG-UI (CopilotKit/AWS/Microsoft):
POST /v1/publish/ag-ui— accepts AG-UI event streams and translates them to blocks
This makes Surflet a unified persistent rendering layer for multi-protocol agent ecosystems. See the API Reference.
Internationalization (i18n)
Surflet's UI text ("Created at", "Powered by Surflet", "Action recorded", etc.) automatically switches language based on the locale field.
Set it in the payload:
{ "locale": "zh-CN", "blocks": [...] }
Supported locales:
| locale | Language |
|---|---|
en-US | English (default) |
zh-CN | Simplified Chinese |
ja-JP | Japanese |
Content provided by the agent (titles, block data) is not affected — whatever language the agent writes in is what appears on the page. locale only affects Surflet's own UI text and date formatting.
Dates are automatically formatted according to locale:
en-US: "Mar 28, 2026"zh-CN: "2026年3月28日"ja-JP: "2026年3月28日"
PWA (Progressive Web App)
Surflet pages can be installed as a PWA on a phone or desktop home screen. User experience:
- Receive a
https://hai.surf/p/xxxlink - Open it in the browser; a prompt appears at the bottom to "Install Surflet"
- After installation → full-screen app experience with no browser address bar
- Next time a link is received → opens directly in the app
App features:
- 📥 Inbox — view all received pages
- ✨ Playground — edit and preview online
- ⚙ Settings — API Key management
Offline support: Previously visited pages are cached in the Service Worker and remain accessible offline.
Install URL: https://hai.surf/app