Skip to content

Client SDK

Discovery

Discover Surf-enabled websites

Client SDK — Discovery#

The @surfjs/client package provides a full-featured SDK for interacting with any Surf-enabled website.

TypeScript
import { SurfClient } from '@surfjs/client'
 
// Discover a Surf-enabled site (fetches manifest automatically)
const client = await SurfClient.discover('https://shop.example.com')
 
// Or with options
const client = await SurfClient.discover('https://shop.example.com', {
auth: 'sk-my-token',
discoverTimeout: 5000,
})
 
// Inspect the manifest
console.log(client.manifest.name) // 'My Store'
console.log(client.manifest.version) // '1.0.0'
console.log(client.manifest.checksum) // 'a1b2c3...'
 
// List all commands
const cmds = client.commands
// => { search: { description: '...', params: {...} }, 'cart.add': {...} }
 
// Get a specific command
const cmd = client.command('search')
// => { description: '...', params: {...}, hints: {...} }
 
// Create from existing manifest (skip discovery)
const client2 = SurfClient.fromManifest(manifest, { baseUrl: 'https://...' })

SurfClientOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | auth | string? | — | Bearer token sent with every request | | discoverTimeout | number? | 5000 | Timeout (ms) for the manifest fetch during discovery | | retry | RetryConfig? | — | Retry configuration for transient failures | | cache | CacheConfig? | — | Cache configuration for idempotent commands | | basePath | string? | /surf/execute | Custom execute path (for @surfjs/next or custom routers) | | fetch | typeof fetch? | globalThis.fetch | Custom fetch implementation |


discoverManifest — Standalone Utility

Fetch and parse a manifest from a URL without creating a full SurfClient. Useful for inspection, validation, or building custom tooling:

TypeScript
import { discoverManifest } from '@surfjs/client'
 
const manifest = await discoverManifest('https://shop.example.com')
// Fetches /.well-known/surf.json and parses it
 
console.log(manifest.name) // 'My Store'
console.log(manifest.commands) // { search: {...}, 'cart.add': {...} }
console.log(manifest.surf) // '0.2' (protocol version)

discoverManifest probes /.well-known/surf.json and validates the response. It throws SurfClientError with code INVALID_MANIFEST if the URL is not Surf-enabled or the manifest is malformed.

TypeScript
import { discoverManifest, SurfClientError } from '@surfjs/client'
 
try {
const manifest = await discoverManifest('https://example.com')
} catch (err) {
if (err instanceof SurfClientError && err.code === 'INVALID_MANIFEST') {
console.log('Not a Surf-enabled site')
}
}

Transports#

SurfClient uses a transport layer internally. By default, SurfClient.discover uses HttpTransport. You can construct a client with a different transport explicitly:

HttpTransport

Standard HTTP transport — one request per execute() call. Used internally by SurfClient.discover():

TypeScript
import { HttpTransport } from '@surfjs/client'
 
const transport = new HttpTransport({
baseUrl: 'https://shop.example.com',
auth: 'sk-token',
fetch: globalThis.fetch,
basePath: '/surf/execute',
})

WebSocketTransport

Persistent WebSocket transport — lower latency for frequent calls. Supports auto-reconnect, configurable backoff, and ping keepalive:

TypeScript
import { WebSocketTransport } from '@surfjs/client'
 
const transport = new WebSocketTransport({
reconnect: true,
maxReconnectAttempts: Infinity,
reconnectDelay: 1000,
maxReconnectDelay: 16000,
pingInterval: 30000,
onStateChange: (state) => console.log('WS state:', state),
onDisconnect: () => console.log('Disconnected'),
onReconnect: () => console.log('Reconnected'),
})
 
await transport.connect('wss://shop.example.com/surf/ws', 'sk-token')
 
// Execute commands over WebSocket
const result = await transport.execute('search', { query: 'laptop' })
 
// Check connection state
console.log(transport.state) // 'connected' | 'connecting' | 'disconnected' | 'reconnecting'
 
// Close when done (stops reconnection)
transport.close()

Or use the client's built-in connect():

TypeScript
const client = await SurfClient.discover('https://shop.example.com')
const ws = await client.connect()
const result = await ws.execute('search', { query: 'laptop' })
client.disconnect()

WindowTransport

Browser-only transport that uses window.__surf__ — for agent scripts running inside a Surf-enabled web app:

TypeScript
import { WindowTransport } from '@surfjs/client'
 
const transport = new WindowTransport()
await transport.connect() // Waits for window.__surf__ or 'surf:ready' event
 
const result = await transport.execute('search', { query: 'laptop' })

WindowTransport throws SurfClientError with code NOT_SUPPORTED when used outside a browser context.