Surflet

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",
)
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