Surflet

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
StatusDescriptionInteractable
draftDraft, not yet publishedEditable
activePublished and accessibleUsers can execute Actions
expiredPast its expiry timeRead-only
completedAll actions completedRead-only
revokedRevoked by the publisherNot accessible
archivedArchivedRead-only

Page Types

Typepage_typeUse Cases
BriefingbriefingResearch summaries, meeting prep, daily reports
ApprovalapprovalRefund approvals, purchase approvals, access requests
ReviewreviewCode diffs, document edits, proposal comparisons
FormformCollecting user input, surveys
DashboarddashboardData visualization, metrics overview
CustomcustomUse 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:

TypePurposeExamples
key_valueKey-value displayTicket info, user profiles
textMarkdown rich textAnalysis reports, documentation
tableData tablesOrder lists, comparison data
calloutCallout / alert boxesAI suggestions, risk warnings
codeCode blocksLogs, configs, scripts
timelineEvent timelinesEvent history, process flows
chartChartsTrend charts, distributions (bar/line/pie/area/scatter/radar)
diffDiff viewsCode changes, text edits
galleryImage galleriesScreenshots, attachments, photos
formInteractive formsData collection
embedEmbedded contentiframes, PDFs, videos
dividerDividersVisual separation
metricMetric cardsKPIs, statistics
buttonsButton groupsQuick actions
logsLog streamsReal-time logs, build output
progressProgress barsTask progress, completion rates
stepperStep indicatorsMulti-step processes
imageImagesSingle image display
countdownCountdown timersDeadlines, time-limited events
comparisonComparison cardsOption comparisons, A/B tests
kanbanKanban boardsTask management, status boards
treeTree structuresFile trees, org charts
gaugeGaugesHealth scores, utilization
sparklineSparklinesInline trend charts
funnelFunnel chartsConversion analysis
threadComment threadsDiscussion threads, chat logs
fileFile attachmentsDownload links, document attachments
heatmapHeatmapsCalibration 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": [...] }
FieldDescription
mode"dark" or "light" (default)
accentPrimary accent color (default #3b82f6)
bg / text / borderOptional 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 callbacks
  • label — display text
  • style — button style (primary / secondary / danger)
  • requires_comment — whether the user must provide a reason
  • confirm_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 step
  • unanimous — all approvers must agree
  • threshold — passes when a minimum number of approvals is reached
  • weighted — weighted voting

See the Approval Workflows guide.

Access Control

Six modes control who can view and interact with a page:

ModeDescription
publicAnyone with the link can access
authenticatedOnly specified identities can access
one_timeSingle-use link, self-destructs after viewing
view_limitedLimited number of views
time_limitedExpires at a specified time
passwordPassword-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 button
  • approval.step_completed — an approval step completed
  • approval.chain_completed — the entire approval chain completed
  • page.expired — the page expired
  • page.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:

  1. POST /v1/publish — create the initial page (can include a "loading..." indicator)
  2. PATCH /v1/pages/:id/blocks — append new blocks (can be called multiple times)
  3. The client receives blocks.appended events in real time via SSE
  4. 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:

TemplateUse Case
refund_approvalRefund approval
deploy_approvalDeployment approval
daily_briefingDaily briefing
incident_reportIncident report
expense_approvalExpense approval
access_requestAccess request
customer_surveyCustomer survey
code_reviewCode review
contract_reviewContract review
onboarding_checklistEmployee 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:

localeLanguage
en-USEnglish (default)
zh-CNSimplified Chinese
ja-JPJapanese

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:

  1. Receive a https://hai.surf/p/xxx link
  2. Open it in the browser; a prompt appears at the bottom to "Install Surflet"
  3. After installation → full-screen app experience with no browser address bar
  4. 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