Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

surf-api

Python SDK for the Surf social platform API.

Installation

pip install surf-api

Quick Start

from surf_api import SurfClient

client = SurfClient("surf_sk_live_your_token_here")

# Get feed metadata
feed = client.feeds.get("surf/topic/technology")
print(feed["title"])

# Get posts from a feed
data = client.feeds.get_posts("surf/topic/technology", limit=10)
for post in data["posts"]:
    print(f"{post['title']} by {post['author']['displayName']}")

# Search for feeds
results = client.search.feeds("artificial intelligence")

# Natural language search (NLWeb)
results = client.search.ask("feeds about sustainable energy", k=5)

# Get trending posts
trending = client.feeds.get_posts("surf/trending/dynamic", limit=10)

Features

  • Feeds: Read feeds, posts, trending content, topics
  • Search: Search feeds, posts, accounts, Bluesky users, podcasts, RSS, publications
  • Longform: standard.site / Leaflet documents & publications
  • AI Search: Natural language queries via NLWeb
  • Audio: Radio stations, daily briefings, transcripts, quizzes
  • Custom Feeds: Create, update, delete, clone, publish custom feeds
  • Account: User info, activity, notifications, preferences
  • Media: Upload images
  • Rate Limiting: Automatic rate limit tracking via client.rate_limit
  • Pagination: Built-in cursor-based pagination

Authentication

Get an API token from the Surf Developer Portal.

client = SurfClient("surf_sk_live_your_token_here")

# Check rate limit status after any request
print(client.rate_limit)  # RateLimitInfo(remaining=59/60, reset=2026-01-01T00:01:00Z)

Custom Feeds

# Create a custom feed
feed = client.custom_feeds.create(
    title="AI News",
    description="Latest AI and ML news",
    operators=[
        {"surfId": "surf/topic/artificial-intelligence", "operator": "source"},
        {"surfId": "bluesky/user/@ai-news.bsky.social", "operator": "source"},
        {"surfId": "surf/hashtag/machinelearning", "operator": "source"},
    ]
)

# Add a source
client.custom_feeds.add_operator(feed["id"], {
    "surfId": "surf/search_keyword/machine learning", "operator": "source"
})

# Publish it
client.custom_feeds.publish(feed["id"])

# Create a themed feed
from surf_api.client import FeedTheme

theme = FeedTheme(
    header_image="https://cdn.example.com/logo.png",
    header_image_size={"width": 600, "height": 272},
    surface="#EFEADD",
    surface_header="#005F5F",
)
feed = client.custom_feeds.create("Branded Feed", theme=theme)

Longform (standard.site / Leaflet)

Documents and publications are addressed by AT-URI — pass the raw URI, the SDK percent-encodes it for you.

# Fetch a document (HTML by default; format="blocks" for structured pages)
doc = client.longform.document("at://did:plc:x/site.standard.document/3k2a")
print(doc["title"], doc["content_html"])

# Fetch a publication
pub = client.longform.publication("at://did:plc:x/site.standard.publication/self")

# List a publication's documents (offset maps to the API's `from` param)
docs = client.longform.publication_documents(pub["uri"], tags=["essays"], count=50)

# Search publications (also available as client.search.publications)
pubs = client.longform.search_publications("climate")

Posts in feed and search responses may include an optional document summary (title, description, cover_image_url, tags, publication_uri) when they link to a longform document.

Audio

# Create a radio station from a feed
station = client.audio.create_station(feed_surf_id="surf/topic/technology")

# Generate a program
program = client.audio.generate_program(station["id"])

# Get the audio manifest
manifest = client.audio.get_program(station["id"])

# Get today's briefing
briefing = client.audio.get_briefing()

# Get a transcript
transcript = client.audio.get_transcript("surf/post/abc123")

Error Handling

from surf_api import SurfClient, SurfRateLimitError, SurfAuthError, SurfNotFoundError

client = SurfClient("surf_sk_live_your_token_here")

try:
    feed = client.feeds.get("surf/topic/nonexistent")
except SurfNotFoundError:
    print("Feed not found")
except SurfRateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
except SurfAuthError:
    print("Invalid API token")

Configuration

client = SurfClient(
    api_key="surf_sk_live_...",
    base_url="https://api.surf.social",  # default
    timeout=30,                           # request timeout in seconds
)

AI Agent

Build autonomous agents that interact with the social web. Requires claude-agent-sdk:

pip install claude-agent-sdk
import asyncio
from surf_api.agent import SurfAgent

async def main():
    agent = SurfAgent(surf_api_key="surf_sk_live_your_token")
    result = await agent.run(
        "Find the top AI feeds on Surf and summarize the latest posts"
    )
    print(result.text)

asyncio.run(main())

The agent connects to Surf via MCP with 20 read-only tools enabled by default. To also allow posting, favouriting, and feed creation (24 tools total), set allow_writes=True:

agent = SurfAgent(surf_api_key="surf_sk_live_your_token", allow_writes=True)

All Claude compute runs on your Agent SDK credit.

MCP Integration

The Surf API is also available as an MCP server for Claude Code and other MCP-compatible clients. See the API documentation for details.

Links