Python SDK
Python SDK installation and usage guide
Installation
pip install surflet
Install from source for local development:
pip install -e ./sdk/python
Initialize the Client
import surflet
client = surflet.Client(
api_key="sk_live_...", # required
base_url="https://api.surflet.app", # optional, defaults to production
timeout=30, # optional, request timeout in seconds
)
You can also configure via environment variables:
export SURFLET_API_KEY=sk_live_...
export SURFLET_API_URL=https://api.surflet.app
client = surflet.Client() # automatically reads environment variables
Quick Publish (Markdown)
The simplest approach — pass a title and Markdown content:
page = client.publish(
title="Customer Support Ticket Analysis #4821",
content="## Analysis\n\nCustomer reported a product quality issue. Recommend full refund.\n\n**Risk Level**: Low",
)
print(f"Page published: {page['pageUrl']}")
Structured Publish
Use Block builders to create rich structured content:
page = client.publish(
title="Refund Approval #4821",
page_type="approval",
blocks=[
surflet.KeyValue("Ticket Overview", [
{"key": "Customer", "value": "Alice Wang"},
{"key": "Order ID", "value": "#ORD-20260321-7891"},
{"key": "Amount", "value": "$127.50"},
{"key": "Reason", "value": "Product arrived damaged"},
]),
surflet.Callout(
"Recommendation: Full refund. Good history, first complaint.",
style="info",
),
surflet.Table(
columns=[
{"key": "date", "label": "Date"},
{"key": "event", "label": "Event"},
{"key": "detail", "label": "Detail"},
],
rows=[
{"date": "2026-03-20", "event": "Order placed", "detail": "Standard shipping"},
{"date": "2026-03-22", "event": "Delivered", "detail": "Signed by recipient"},
{"date": "2026-03-22", "event": "Complaint", "detail": "Product casing damaged"},
],
title="Timeline",
),
],
actions=[
surflet.Action("act_approve", "Approve Refund", style="primary"),
surflet.Action("act_reject", "Reject", style="danger", requires_comment=True),
],
access=surflet.Access.authenticated(emails=["[email protected]"]),
on_action={"default_url": "https://your-agent.example.com/webhook/surflet"},
notify="slack:#cs-approvals",
)
Block Builders
KeyValue — Key-Value Pairs
surflet.KeyValue("Title", [
{"key": "Field name", "value": "Field value"},
{"key": "Field name 2", "value": "Field value 2"},
])
Text — Markdown Rich Text
surflet.Text("## Analysis Report\n\nDetailed content...", title="Detailed Analysis")
Table — Data Table
surflet.Table(
columns=[
{"key": "name", "label": "Name"},
{"key": "role", "label": "Role"},
],
rows=[
{"name": "Alice", "role": "Manager"},
{"name": "Bob", "role": "Engineer"},
],
title="Team Members",
)
Callout — Callout Box
surflet.Callout("This is an important notice", style="info") # info / warning / error / success
Code — Code Block
surflet.Code("console.log('hello')", language="javascript", title="Example Code")
Timeline — Timeline
surflet.Timeline([
{"time": "2026-03-20 10:00", "event": "Ticket created", "detail": "User submitted"},
{"time": "2026-03-20 11:30", "event": "AI analysis", "detail": "Refund recommended"},
{"time": "2026-03-20 14:00", "event": "Manager approval", "detail": "Pending"},
], title="Process Flow")
Chart — Chart
surflet.Chart(
chart_type="bar",
data={
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{"label": "Revenue", "data": [100, 200, 150, 300]}],
},
title="Quarterly Revenue",
)
Diff — Diff View
surflet.Diff(
old_text="Old version content",
new_text="New version content",
language="python",
title="Config Changes",
)
Gallery — Image Gallery
surflet.Gallery([
{"url": "https://example.com/img1.png", "caption": "Product damage photo"},
{"url": "https://example.com/img2.png", "caption": "Packaging condition"},
], title="Evidence")
Form — Interactive Form
surflet.Form([
{"field_id": "amount", "type": "number", "label": "Refund Amount", "required": True},
{"field_id": "reason", "type": "textarea", "label": "Reason for Refund"},
], title="Refund Details")
Embed — Embedded Content
surflet.Embed(url="https://example.com/report.pdf", embed_type="pdf", title="Original Report")
Divider — Divider
surflet.Divider()
Action (Action Buttons)
surflet.Action(
action_id="act_approve", # unique identifier
label="Approve Refund", # button text
style="primary", # primary / secondary / danger
requires_comment=False, # whether a reason is required
confirm_message="Confirm approval?", # confirmation dialog text
)
Access Control
# Public access
access = surflet.Access.public()
# Specific emails only
access = surflet.Access.authenticated(emails=["[email protected]", "[email protected]"])
# Single-use link (self-destructs after viewing)
access = surflet.Access.one_time(burn_after=300) # destroy 5 minutes after viewing
# Time-limited access
access = surflet.Access.time_limited(expires_at="2026-04-01T00:00:00Z")
# Password protection
access = surflet.Access.password(password_hash="sha256:...")
Approval Chain
approval_chain = {
"mode": "sequential", # sequential / parallel / conditional
"steps": [
{
"step_id": "step_manager",
"name": "Manager Approval",
"assignees": [{"type": "email", "value": "[email protected]"}],
"policy": {"type": "any"},
"timeout_hours": 24,
"on_timeout": "escalate",
},
{
"step_id": "step_finance",
"name": "Finance Confirmation",
"assignees": [
{"type": "email", "value": "[email protected]"},
{"type": "email", "value": "[email protected]"},
],
"policy": {"type": "threshold", "min_approvals": 1},
},
],
"on_reject_at_any_step": "halt",
}
page = client.publish(
title="Purchase Approval",
page_type="approval",
approval_chain=approval_chain,
# ...other parameters
)
Callback Configuration
on_action = {
"default_url": "https://your-agent.example.com/webhook/surflet",
"hmac_secret": "your-secret-key", # optional, enables HMAC signature verification
"events": ["action.executed", "approval.chain_completed"], # optional, filter event types
}
Publish from Template
Publish a complete page from a preset template in one line:
# List available templates
templates = client.list_templates()
for t in templates["templates"]:
print(f"{t['name']}: {t['description']}")
# Publish from template
page = client.publish_template("refund_approval", variables={
"customer": "Alice Wang",
"order_id": "#ORD-7891",
"amount": 127.50,
"reason": "Product arrived damaged",
"risk_score": 15,
})
print(f"Page: {page['pageUrl']}")
10 templates available: refund_approval, deploy_approval, daily_briefing, incident_report, expense_approval, access_request, customer_survey, code_review, contract_review, onboarding_checklist. See the Templates guide.
Append Blocks (Streaming Publish)
Publish an initial page first, then progressively append analysis results:
# Step 1: Create the initial page
page = client.publish(
title="Analysis Report",
blocks=[surflet.Callout("Analyzing data...", style="info")],
)
page_id = page["pageId"]
# Step 2: Append analysis results
client.append_blocks(page_id, blocks=[
surflet.KeyValue("Key Metrics", [
{"key": "Total Tickets", "value": "156"},
{"key": "Resolution Rate", "value": "94%"},
]),
surflet.Chart(
chart_type="line",
data={
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"datasets": [{"label": "Tickets", "data": [28, 35, 22, 41, 30]}],
},
title="Daily Ticket Trend",
),
])
# Can append multiple times
client.append_blocks(page_id, blocks=[
surflet.Text("## Summary\n\nTicket resolution efficiency improved by 12% this week.", title="Summary"),
])
See the Streaming Publish guide.
Page Analytics
# Get page view analytics
analytics = client.get_analytics("pg_xxx")
print(f"Total views: {analytics['totalViews']}")
print(f"Unique viewers: {analytics['uniqueViewers']}")
for date, views in analytics["dailyViews"].items():
print(f" {date}: {views} views")
Agent Quality Score
# Get agent quality score
score = client.get_quality_score()
print(f"Quality Score: {score['score']}/100")
print(f"Acceptance Rate: {score['acceptanceRate']}%")
print(f"Avg Decision Time: {score['avgDecisionTimeMinutes']} minutes")
Query Pages
# Get page details
page = client.get_page("pg_xxx")
print(page["status"]) # "active" / "completed" / "expired"
# Get approval status
approval = client.get_approval("pg_xxx")
print(approval["current_step"])
Revoke a Page
client.revoke_page("pg_xxx")
Inbox
# Get pending items
inbox = client.get_inbox(status="pending")
for item in inbox["items"]:
print(f"{item['title']} - {item['priority']}")
# Pagination
inbox = client.get_inbox(status="pending", page=2, per_page=20)
Approval Actions
# Approve
client.approve("pg_xxx", comment="Looks good")
# Reject
client.reject("pg_xxx", comment="Need more details")
Full Example — Agent Integration
import surflet
surflet_client = surflet.Client(
api_key="sk_live_...",
base_url="https://api.surflet.app",
)
class RefundHandler:
"""Agent handler for processing refund requests"""
def handle_refund_request(self, ticket):
analysis = self.analyze_ticket(ticket)
page = surflet_client.publish(
title=f"Refund Approval - Ticket #{ticket.id}",
page_type="approval",
blocks=[
surflet.KeyValue("Ticket Info", [
{"key": "Customer", "value": ticket.customer_name},
{"key": "Amount", "value": f"${ticket.amount:.2f}"},
{"key": "Product", "value": ticket.product_name},
]),
surflet.Callout(
f"AI Recommendation: {analysis.recommendation}. "
f"Confidence: {analysis.confidence:.0%}",
style="info" if analysis.confidence > 0.8 else "warning",
),
surflet.Text(analysis.detailed_report, title="Detailed Analysis"),
],
actions=[
surflet.Action("act_approve", "Approve Refund", style="primary"),
surflet.Action("act_partial", "Partial Refund", style="secondary"),
surflet.Action("act_reject", "Reject",
style="danger", requires_comment=True),
],
approval_chain={
"mode": "sequential",
"steps": [{
"step_id": "review",
"name": "Supervisor Approval",
"assignees": [
{"type": "email", "value": ticket.supervisor_email},
],
"policy": {"type": "any"},
"timeout_hours": 48,
}],
},
on_action={
"default_url": "https://your-agent.example.com/api/surflet/callback",
},
notify=f"slack:#{ticket.team_channel}",
tags=["refund", f"team:{ticket.team}"],
)
ticket.update(
surflet_page_id=page["pageId"],
surflet_url=page["pageUrl"],
status="pending_approval",
)
return page