Surflet

Quickstart

Publish your first page in 5 minutes

Try it online

No installation needed. Open the Playground in your browser — edit JSON on the left, preview the rendered result on the right. Four preset templates are available: briefing, approval, dashboard, and form.

Install the SDK

Pick the SDK for your stack:

Python:

pip install surflet
# or install from local source
pip install -e ./sdk/python

TypeScript:

pnpm add @surflet/sdk
# or
npm install @surflet/sdk

Get an API Key

Create an API Key in the Surflet console. For local development, use a test key:

export SURFLET_API_KEY=sk_test_surflet123
export SURFLET_API_URL=http://localhost:3001

Test keys start with sk_test_ and point to your local dev server. Production keys start with sk_live_.

Your first page — Markdown publish

The simplest approach: pass a title and Markdown content, and publish in one line.

import surflet

client = surflet.Client(
    api_key="sk_test_surflet123",
    base_url="http://localhost:3001",
)

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']}")

Open the returned URL to see the rendered page.

Structured approval page

In practice, you'll usually want richer structured content with an approval workflow:

import surflet

client = surflet.Client(
    api_key="sk_test_surflet123",
    base_url="http://localhost:3001",
)

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

print(f"Approval page: {page['pageUrl']}")
print(f"Page ID: {page['pageId']}")

Receive callbacks

When a user takes an action on the page (e.g., clicks "Approve"), Surflet POSTs to your configured callback URL:

# FastAPI example
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhook/surflet")
async def handle_surflet_callback(request: Request):
    event = await request.json()

    if event["event"] == "action.executed":
        action = event["data"]["action_id"]
        actor = event["actor"]["identity"]

        if action == "act_approve":
            process_refund(event["page_id"])
        else:
            notify_cs_agent(event["page_id"], event["data"].get("comment"))

    return {"ok": True}

Publish from a template

If your use case matches a preset template, you can publish a complete structured page in one line:

page = client.publish_template("refund_approval", variables={
    "customer": "Alice Wang",
    "order_id": "#ORD-20260321-7891",
    "amount": 127.50,
    "reason": "Product arrived damaged",
    "risk_score": 15,
})
print(f"Approval page: {page['pageUrl']}")

Templates automatically generate combinations of key_value, gauge, callout, and stepper blocks, with pre-configured approval chains and action buttons. 10 templates are available — see the Templates guide.

Next steps