Skip to content

Getting Started

Quick Start

Get started with Surf in under 20 lines

Quick Start#

A complete Surf server with commands. Pick your framework:

app/api/surf/[[...slug]]/route.ts
import { createSurf } from '@surfjs/core'
import { createSurfRouteHandler } from '@surfjs/next'
 
const surf = await createSurf({
name: 'My Store',
commands: {
search: {
description: 'Search products',
params: {
query: { type: 'string', required: true },
limit: { type: 'number', default: 10 },
},
run: async ({ query, limit }) => {
return db.products.search(query, { limit })
},
},
},
})
 
export const { GET, POST } = createSurfRouteHandler(surf)

Now any AI agent can discover and use your commands:

TypeScript
import { SurfClient } from '@surfjs/client'
 
const client = await SurfClient.discover('http://localhost:3000')
 
// List available commands
console.log(client.commands)
// => { search: { description: 'Search products', params: {...} } }
 
// Execute a command
const result = await client.execute('search', { query: 'laptop' })
// => { results: [...], total: 42 }

Optional: Add a SurfBadge#

If your app has a frontend, you can add a visual badge that helps AI vision models discover your Surf commands:

TSX
import { SurfBadge } from '@surfjs/react'
 
<SurfBadge
endpoint="https://mystore.com"
name="My Store"
commands={[{ name: 'search', description: 'Search products' }]}
/>

The badge is optional — discovery works via /.well-known/surf.json without it. See the @surfjs/react docs for details.

For Browser-Based Agents#

When your app includes SurfProvider or SurfBadge, window.surf is automatically available. But window.surf isn't just an HTTP wrapper — it's a local runtime that can execute commands directly in the browser.

Server commands (default)

Without any client-side setup, window.surf proxies all commands to your server:

JavaScript
// Agent in browser
const result = await window.surf.execute('search', { query: 'laptop' })
// => Proxied to server → HTTP POST → response

Local commands (with useSurfCommands)

Register local handlers to make commands execute in the browser — no server round-trip:

TSX
import { useSurfCommands } from '@surfjs/react'
 
function App() {
useSurfCommands({
'search': {
mode: 'local',
run: ({ query }) => {
const results = searchIndex.search(query)
return { ok: true, result: results }
}
}
})
 
return <MyApp />
}

Now agents can interact instantly:

JavaScript
// Agent in browser — local handler runs, no network
await window.surf.execute('search', { query: 'laptop' })
// => { ok: true, result: [...] } — instant

This is the primary way browser-based agents interact with Surf. See Architecture & Execution Models for the three execution strategies and when to use each.

Framework-Agnostic (@surfjs/web)

Not using React? @surfjs/web is the core browser runtime — use it with any framework or plain JavaScript:

JavaScript
import { initSurf, registerCommand } from '@surfjs/web'
 
// Initialize window.surf
initSurf({ endpoint: 'https://myapp.com' })
 
// Register local command handlers
registerCommand('search', {
mode: 'local',
run: ({ query }) => {
const results = searchIndex.search(query)
return { ok: true, result: results }
}
})
 
// Now agents can use window.surf.execute('search', { query: 'laptop' })

@surfjs/react's useSurfCommands is a thin wrapper around registerCommand that handles React lifecycle (cleanup on unmount, re-registration on change). The runtime is the same.