Core API

Namespacing

Organize commands with dot notation and deep nesting

Namespacing#

Organize related commands using dot notation. Nest command groups to create clean, discoverable APIs.

typescript
const surf = await createSurf({
name: 'My Store',
commands: {
// Top-level command
search: {
description: 'Search products',
params: { query: { type: 'string', required: true } },
run: async ({ query }) => db.search(query),
},
ย 
// Nested namespace using groups
cart: {
add: {
description: 'Add item to cart',
params: { sku: { type: 'string', required: true } },
auth: 'required',
run: async ({ sku }, ctx) => cart.add(ctx.sessionId, sku),
},
remove: {
description: 'Remove item from cart',
params: { sku: { type: 'string', required: true } },
auth: 'required',
run: async ({ sku }, ctx) => cart.remove(ctx.sessionId, sku),
},
view: {
description: 'View cart contents',
auth: 'required',
run: async (_, ctx) => cart.get(ctx.sessionId),
},
},
ย 
// Deep nesting
user: {
profile: {
get: {
description: 'Get user profile',
auth: 'required',
run: async (_, ctx) => users.getProfile(ctx.claims?.userId),
},
},
},
},
})

This creates commands accessible as search, cart.add, cart.remove, cart.view, and user.profile.get.

Deep Nesting

Surf supports arbitrary nesting depth โ€” for namespaces, object parameters, and return types. This lets you model complex, real-world data structures while keeping everything typed and validated.

Nested Namespaces

Command groups can nest multiple levels deep. Each level creates a dot-separated namespace in the manifest.

typescript
const surf = await createSurf({
name: 'My Store',
commands: {
cart: {
items: {
add: {
description: 'Add an item to the cart',
params: { sku: { type: 'string', required: true } },
auth: 'required',
run: async ({ sku }, ctx) => cart.addItem(ctx.sessionId!, sku),
},
remove: {
description: 'Remove an item from the cart',
params: { sku: { type: 'string', required: true } },
auth: 'required',
run: async ({ sku }, ctx) => cart.removeItem(ctx.sessionId!, sku),
},
list: {
description: 'List all items in the cart',
auth: 'required',
run: async (_, ctx) => cart.listItems(ctx.sessionId!),
},
},
checkout: {
description: 'Checkout the current cart',
auth: 'required',
run: async (_, ctx) => cart.checkout(ctx.sessionId!),
},
},
user: {
profile: {
get: {
description: 'Get user profile',
auth: 'required',
run: async (_, ctx) => users.getProfile(ctx.claims?.userId),
},
update: {
description: 'Update user profile',
auth: 'required',
params: {
name: { type: 'string' },
bio: { type: 'string' },
},
run: async (params, ctx) => users.updateProfile(ctx.claims?.userId, params),
},
},
},
},
})

This produces commands: cart.items.add, cart.items.remove, cart.items.list, cart.checkout, user.profile.get, and user.profile.update.

Nested Object Parameters

Use type: 'object' with properties to define nested parameter structures. Nesting can go as deep as needed.

typescript
{
params: {
address: {
type: 'object',
description: 'Shipping address',
properties: {
street: { type: 'string', required: true },
city: { type: 'string', required: true },
zip: { type: 'string' },
country: { type: 'string', default: 'US' },
coordinates: {
type: 'object',
properties: {
lat: { type: 'number', required: true },
lng: { type: 'number', required: true },
},
},
},
},
},
}

Nested Array Parameters

Arrays use items to define element schemas โ€” including nested objects and arrays of arrays.

typescript
{
params: {
// Array of strings
tags: {
type: 'array',
items: { type: 'string' },
},
ย 
// Array of objects
lineItems: {
type: 'array',
items: {
type: 'object',
properties: {
sku: { type: 'string', required: true },
quantity: { type: 'number', default: 1 },
options: {
type: 'array',
items: { type: 'string' },
},
},
},
},
},
}

Reusable Types with $ref

Define complex types once in types and reference them with $ref anywhere โ€” in params, return schemas, or array items. This avoids duplication and keeps your manifest clean.

typescript
const surf = await createSurf({
name: 'My Store',
types: {
Address: {
type: 'object',
description: 'A postal address',
properties: {
street: { type: 'string', required: true },
city: { type: 'string', required: true },
zip: { type: 'string' },
country: { type: 'string' },
},
},
LineItem: {
type: 'object',
description: 'A single item in an order',
properties: {
sku: { type: 'string', required: true },
quantity: { type: 'number', default: 1 },
price: { type: 'number', required: true },
},
},
},
commands: {
'order.create': {
description: 'Place a new order',
auth: 'required',
params: {
shippingAddress: { $ref: 'Address' },
billingAddress: { $ref: 'Address' },
items: { type: 'array', items: { $ref: 'LineItem' } },
notes: { type: 'string' },
},
returns: {
type: 'object',
properties: {
orderId: { type: 'string' },
total: { type: 'number' },
items: { type: 'array', items: { $ref: 'LineItem' } },
},
},
run: async (params, ctx) => {
return orders.create(ctx.claims!.userId, params)
},
},
},
})

๐Ÿ’ก Tip: Types referenced by $ref are included in the manifest under the types key, making them visible to agents for schema understanding.