Functions API
Execute business functions with type-safe inputs and outputs
Execute business functions with type-safe inputs and outputs. Functions are the core building blocks of Business-as-Code, representing reusable business logic that can be composed, tested, and executed.
Overview
Business functions are executable units of business logic that:
- Accept typed inputs and return typed outputs
- Can be composed into workflows
- Support metadata and documentation
- Are versioned and testable
- Can be executed synchronously or asynchronously
import { createBusinessApi } from 'business-as-code'
const api = createBusinessApi({
apiKey: process.env.APIS_DO_KEY,
})
// Execute a function
const result = await api.functions.execute('calculate-discount', {
orderTotal: 150.0,
customerTier: 'premium',
})execute()
Execute a business function with the provided input.
Signature
execute<TInput, TOutput>(
functionId: string,
input: TInput
): Promise<TOutput>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| functionId | string | Yes | Unique identifier of the function to execute |
| input | TInput | Yes | Input data for the function |
Returns
Promise that resolves to the function's output of type TOutput.
Example
// Simple calculation function
const discount = await api.functions.execute<{ orderTotal: number; customerTier: string }, { discountAmount: number; finalTotal: number }>(
'calculate-discount',
{
orderTotal: 150.0,
customerTier: 'premium',
}
)
console.log('Discount:', discount.discountAmount) // 15.00
console.log('Final total:', discount.finalTotal) // 135.00Complex Example: Revenue Calculation
interface RevenueInput {
transactions: Array<{
id: string
amount: number
currency: string
date: string
type: 'sale' | 'refund'
}>
period: {
start: string
end: string
}
aggregateBy?: 'day' | 'week' | 'month'
}
interface RevenueOutput {
total: number
currency: string
breakdown: Record<string, number>
growth: {
amount: number
percentage: number
}
metrics: {
averageTransaction: number
transactionCount: number
refundRate: number
}
}
const revenue = await api.functions.execute<RevenueInput, RevenueOutput>('calculate-revenue', {
transactions: [
{ id: 'txn-1', amount: 99.99, currency: 'USD', date: '2024-10-01', type: 'sale' },
{ id: 'txn-2', amount: 149.99, currency: 'USD', date: '2024-10-15', type: 'sale' },
{ id: 'txn-3', amount: 49.99, currency: 'USD', date: '2024-10-20', type: 'refund' },
],
period: {
start: '2024-10-01',
end: '2024-10-31',
},
aggregateBy: 'month',
})
console.log('Total Revenue:', revenue.total)
console.log('Growth:', revenue.growth.percentage + '%')
console.log('Average Transaction:', revenue.metrics.averageTransaction)Error Handling
import { ApiError, ValidationError } from 'business-as-code'
try {
const result = await api.functions.execute('calculate-discount', {
orderTotal: 150.0,
customerTier: 'premium',
})
} catch (error) {
if (error instanceof ValidationError) {
// Client-side validation failed
console.error(`Invalid ${error.field}: ${error.message}`)
} else if (error instanceof ApiError) {
if (error.statusCode === 404) {
console.error('Function not found')
} else if (error.statusCode === 422) {
console.error('Invalid input:', error.body)
} else {
console.error('Execution failed:', error.message)
}
}
}Validation
The execute() method performs the following validations:
functionIdmust be provided and be a stringinputmust be provided
// ❌ Throws ValidationError
await api.functions.execute('', { data: 'value' })
// Error: functionId is required
await api.functions.execute('my-function', null)
// Error: input is required
await api.functions.execute(123, { data: 'value' })
// Error: functionId must be a stringExecution Modes
Functions can execute in different modes:
Synchronous (default):
// Wait for result
const result = await api.functions.execute('quick-calc', { x: 10, y: 20 })
console.log(result) // { sum: 30 }With timeout:
// Add timeout to API call
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)
try {
const result = await api.functions.execute('slow-function', input)
} catch (error) {
if (error.name === 'AbortError') {
console.error('Function timed out after 5 seconds')
}
} finally {
clearTimeout(timeout)
}list()
Retrieve a list of all available functions.
Signature
list(): Promise<BusinessFunction[]>Returns
Promise that resolves to an array of BusinessFunction objects.
Example
const functions = await api.functions.list()
console.log(`Found ${functions.length} functions`)
functions.forEach((fn) => {
console.log(`- ${fn.name} (${fn.id})`)
console.log(` ${fn.description}`)
})Response Type
interface BusinessFunction {
id: string
name: string
description?: string
input?: any
output?: any
execute: (input: any) => Promise<any> | any
metadata?: Record<string, any>
}Filtering and Searching
// Get all functions
const allFunctions = await api.functions.list()
// Filter by category (client-side)
const calculationFunctions = allFunctions.filter((fn) => fn.metadata?.category === 'calculation')
// Search by name (client-side)
const searchTerm = 'discount'
const matchingFunctions = allFunctions.filter(
(fn) => fn.name.toLowerCase().includes(searchTerm.toLowerCase()) || fn.description?.toLowerCase().includes(searchTerm.toLowerCase())
)Complete Example
async function discoverFunctions() {
const functions = await api.functions.list()
// Group by category
const byCategory = functions.reduce(
(acc, fn) => {
const category = fn.metadata?.category || 'uncategorized'
if (!acc[category]) acc[category] = []
acc[category].push(fn)
return acc
},
{} as Record<string, BusinessFunction[]>
)
// Display organized list
Object.entries(byCategory).forEach(([category, fns]) => {
console.log(`\n${category.toUpperCase()}:`)
fns.forEach((fn) => {
console.log(` ${fn.name}`)
console.log(` ID: ${fn.id}`)
if (fn.description) {
console.log(` Description: ${fn.description}`)
}
})
})
return byCategory
}
await discoverFunctions()get()
Retrieve a specific function by its ID.
Signature
get(id: string): Promise<BusinessFunction>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique identifier of the function |
Returns
Promise that resolves to a BusinessFunction object.
Example
const fn = await api.functions.get('calculate-discount')
console.log('Function:', fn.name)
console.log('Description:', fn.description)
console.log('Metadata:', fn.metadata)Error Handling
try {
const fn = await api.functions.get('non-existent-function')
} catch (error) {
if (error instanceof ApiError && error.statusCode === 404) {
console.error('Function not found')
}
}Validation
// ❌ Throws ValidationError
await api.functions.get('')
// Error: id is required
await api.functions.get(null)
// Error: id is required
await api.functions.get(123)
// Error: id must be a stringInspecting Function Details
async function inspectFunction(functionId: string) {
const fn = await api.functions.get(functionId)
console.log('='.repeat(50))
console.log(`Function: ${fn.name}`)
console.log('='.repeat(50))
console.log(`ID: ${fn.id}`)
console.log(`Description: ${fn.description || 'No description'}`)
// Display input schema
if (fn.input) {
console.log('\nInput Schema:')
console.log(JSON.stringify(fn.input, null, 2))
}
// Display output schema
if (fn.output) {
console.log('\nOutput Schema:')
console.log(JSON.stringify(fn.output, null, 2))
}
// Display metadata
if (fn.metadata) {
console.log('\nMetadata:')
Object.entries(fn.metadata).forEach(([key, value]) => {
console.log(` ${key}: ${value}`)
})
}
return fn
}
await inspectFunction('calculate-discount')create()
Create a new business function.
Signature
create(fn: Omit<BusinessFunction, 'id'>): Promise<BusinessFunction>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| fn | object | Yes | Function definition without ID |
Returns
Promise that resolves to the created BusinessFunction with generated ID.
Example
const discountFunction = await api.functions.create({
name: 'Calculate Discount',
description: 'Calculate discount based on order total and customer tier',
input: {
orderTotal: 'number',
customerTier: 'string',
},
output: {
discountAmount: 'number',
finalTotal: 'number',
},
execute: async (input) => {
const discountRates = {
basic: 0.05,
premium: 0.1,
enterprise: 0.15,
}
const rate = discountRates[input.customerTier] || 0
const discountAmount = input.orderTotal * rate
const finalTotal = input.orderTotal - discountAmount
return { discountAmount, finalTotal }
},
metadata: {
category: 'pricing',
version: '1.0.0',
author: 'finance-team',
},
})
console.log('Created function:', discountFunction.id)Complex Example: Tax Calculation
const taxFunction = await api.functions.create({
name: 'Calculate Sales Tax',
description: 'Calculate sales tax based on location and product category',
input: {
type: 'object',
properties: {
subtotal: { type: 'number', minimum: 0 },
location: {
type: 'object',
properties: {
country: { type: 'string' },
state: { type: 'string' },
zipCode: { type: 'string' },
},
required: ['country', 'state'],
},
category: {
type: 'string',
enum: ['physical', 'digital', 'service'],
},
},
required: ['subtotal', 'location', 'category'],
},
output: {
type: 'object',
properties: {
taxAmount: { type: 'number' },
taxRate: { type: 'number' },
total: { type: 'number' },
breakdown: {
type: 'object',
properties: {
state: { type: 'number' },
county: { type: 'number' },
city: { type: 'number' },
},
},
},
},
execute: async (input) => {
// Tax calculation logic
const taxRates = await getTaxRates(input.location)
const applicableRates = filterByCategory(taxRates, input.category)
const breakdown = {
state: input.subtotal * applicableRates.state,
county: input.subtotal * applicableRates.county,
city: input.subtotal * applicableRates.city,
}
const taxAmount = Object.values(breakdown).reduce((a, b) => a + b, 0)
const total = input.subtotal + taxAmount
return {
taxAmount,
taxRate: taxAmount / input.subtotal,
total,
breakdown,
}
},
metadata: {
category: 'tax',
version: '2.1.0',
lastUpdated: new Date().toISOString(),
dependencies: ['tax-rates-service'],
rateLimit: 100,
cacheTTL: 3600,
},
})
console.log('Tax function created:', taxFunction.id)Validation
// ❌ Throws ValidationError
await api.functions.create(null)
// Error: function is required
await api.functions.create({} as any)
// Error: function must be an object
await api.functions.create({ name: 'My Function' } as any)
// Error: execute function is requiredFunction Composition
Create functions that call other functions:
// Create base functions
const priceFunction = await api.functions.create({
name: 'Calculate Base Price',
execute: async (input) => {
return { basePrice: input.quantity * input.unitPrice }
},
})
const discountFunction = await api.functions.create({
name: 'Apply Discount',
execute: async (input) => {
return {
discountedPrice: input.basePrice * (1 - input.discountRate),
}
},
})
// Create composed function
const finalPriceFunction = await api.functions.create({
name: 'Calculate Final Price',
description: 'Compose pricing with discount and tax',
execute: async (input) => {
// Call base price function
const { basePrice } = await api.functions.execute(priceFunction.id, { quantity: input.quantity, unitPrice: input.unitPrice })
// Apply discount
const { discountedPrice } = await api.functions.execute(discountFunction.id, { basePrice, discountRate: input.discountRate })
// Apply tax
const { total } = await api.functions.execute('calculate-sales-tax', { subtotal: discountedPrice, location: input.location, category: input.category })
return { finalPrice: total }
},
metadata: {
category: 'pricing',
composedFrom: [priceFunction.id, discountFunction.id, 'calculate-sales-tax'],
},
})Type Safety
Input/Output Types
Define strict types for function inputs and outputs:
// Define types
interface ProductPricingInput {
productId: string
quantity: number
customerId: string
promoCode?: string
}
interface ProductPricingOutput {
unitPrice: number
subtotal: number
discount: number
tax: number
total: number
savings: number
}
// Create function with types
const pricingFunction = await api.functions.create({
name: 'Product Pricing',
input: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'number', minimum: 1 },
customerId: { type: 'string' },
promoCode: { type: 'string', optional: true },
},
required: ['productId', 'quantity', 'customerId'],
},
output: {
type: 'object',
properties: {
unitPrice: { type: 'number' },
subtotal: { type: 'number' },
discount: { type: 'number' },
tax: { type: 'number' },
total: { type: 'number' },
savings: { type: 'number' },
},
},
execute: async (input: ProductPricingInput): Promise<ProductPricingOutput> => {
// Implementation
return {
unitPrice: 29.99,
subtotal: 29.99 * input.quantity,
discount: 0,
tax: 0,
total: 29.99 * input.quantity,
savings: 0,
}
},
})
// Execute with full type safety
const pricing = await api.functions.execute<ProductPricingInput, ProductPricingOutput>(pricingFunction.id, {
productId: 'prod-123',
quantity: 2,
customerId: 'cust-456',
})
// TypeScript knows the exact shape
console.log(pricing.total) // ✅ number
console.log(pricing.invalid) // ❌ Property 'invalid' does not existFunction Metadata
Standard Metadata Fields
interface FunctionMetadata {
category?: string
version?: string
author?: string
team?: string
lastUpdated?: string
tags?: string[]
dependencies?: string[]
rateLimit?: number
cacheTTL?: number
timeout?: number
retries?: number
visibility?: 'public' | 'private' | 'internal'
deprecated?: boolean
deprecationMessage?: string
successors?: string[]
}
const fn = await api.functions.create({
name: 'Legacy Calculator',
execute: async (input) => ({ result: input.x + input.y }),
metadata: {
category: 'math',
version: '1.0.0',
author: 'engineering-team',
lastUpdated: '2024-10-27T00:00:00Z',
tags: ['math', 'calculator', 'deprecated'],
deprecated: true,
deprecationMessage: 'Use calculate-v2 instead',
successors: ['calculate-v2'],
visibility: 'internal',
},
})Querying by Metadata
async function findFunctionsByMetadata(predicate: (metadata: Record<string, any>) => boolean) {
const functions = await api.functions.list()
return functions.filter((fn) => fn.metadata && predicate(fn.metadata))
}
// Find all deprecated functions
const deprecated = await findFunctionsByMetadata((meta) => meta.deprecated === true)
// Find functions by team
const financeTeam = await findFunctionsByMetadata((meta) => meta.team === 'finance')
// Find recent functions
const recentFunctions = await findFunctionsByMetadata((meta) => {
if (!meta.lastUpdated) return false
const updated = new Date(meta.lastUpdated)
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
return updated > thirtyDaysAgo
})Best Practices
1. Use Descriptive Names
// ❌ Bad
await api.functions.create({
name: 'calc',
execute: async (input) => input.x + input.y,
})
// ✅ Good
await api.functions.create({
name: 'Calculate Order Total',
description: 'Calculate order total including discounts, tax, and shipping',
execute: async (input) => {
// Implementation
},
})2. Document Input/Output
// ✅ Good - Clear schema documentation
await api.functions.create({
name: 'Process Payment',
description: 'Process a payment transaction through the payment gateway',
input: {
type: 'object',
properties: {
amount: {
type: 'number',
description: 'Payment amount in cents',
minimum: 1,
},
currency: {
type: 'string',
description: 'ISO 4217 currency code',
pattern: '^[A-Z]{3}$',
},
paymentMethod: {
type: 'string',
description: 'Payment method ID from payment gateway',
},
},
required: ['amount', 'currency', 'paymentMethod'],
},
execute: async (input) => {
// Implementation
},
})3. Handle Errors Gracefully
await api.functions.create({
name: 'Send Email',
execute: async (input) => {
try {
// Send email
const result = await emailService.send(input)
return { success: true, messageId: result.id }
} catch (error) {
// Log error
console.error('Failed to send email:', error)
// Return error response
return {
success: false,
error: error.message,
retryable: error.code === 'RATE_LIMIT',
}
}
},
})4. Implement Idempotency
await api.functions.create({
name: 'Create Order',
execute: async (input) => {
// Use idempotency key
const idempotencyKey = input.idempotencyKey || crypto.randomUUID()
// Check if already processed
const existing = await db.query('orders').where('idempotencyKey', idempotencyKey).first()
if (existing) {
return existing // Return existing result
}
// Process order
const order = await createOrder(input)
// Store with idempotency key
await db.insert('orders', {
...order,
idempotencyKey,
})
return order
},
})5. Use Versioning
// Version 1
await api.functions.create({
name: 'Calculate Shipping v1',
metadata: { version: '1.0.0' },
execute: async (input) => {
// Simple calculation
return { cost: input.weight * 0.5 }
},
})
// Version 2 with improvements
await api.functions.create({
name: 'Calculate Shipping v2',
metadata: {
version: '2.0.0',
predecessors: ['calculate-shipping-v1'],
},
execute: async (input) => {
// Complex calculation with zones and carriers
return {
cost: calculateByZone(input),
estimatedDays: estimateDelivery(input),
carrier: selectBestCarrier(input),
}
},
})6. Implement Caching
const cache = new Map<string, { data: any; timestamp: number }>()
await api.functions.create({
name: 'Get Exchange Rate',
execute: async (input) => {
const cacheKey = `${input.from}-${input.to}`
const cached = cache.get(cacheKey)
// Return cached if fresh (5 minutes)
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
return cached.data
}
// Fetch fresh data
const rate = await exchangeRateService.get(input.from, input.to)
// Cache result
cache.set(cacheKey, { data: rate, timestamp: Date.now() })
return rate
},
metadata: {
cacheTTL: 300, // 5 minutes
},
})Examples
E-commerce: Price Calculator
const priceCalculator = await api.functions.create({
name: 'E-commerce Price Calculator',
description: 'Calculate final price with discounts, tax, and shipping',
input: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'number' },
unitPrice: { type: 'number' },
},
},
},
customerId: { type: 'string' },
promoCode: { type: 'string', optional: true },
shippingAddress: { type: 'object' },
},
},
execute: async (input) => {
// Calculate subtotal
const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0)
// Apply customer discount
const customer = await api.resources.get('Customer', input.customerId)
const customerDiscount = subtotal * (customer.discountRate || 0)
// Apply promo code
let promoDiscount = 0
if (input.promoCode) {
const promo = await api.resources.get('PromoCode', input.promoCode)
promoDiscount = promo.type === 'percentage' ? subtotal * promo.value : promo.value
}
// Calculate tax
const taxableAmount = subtotal - customerDiscount - promoDiscount
const { taxAmount } = await api.functions.execute('calculate-sales-tax', {
subtotal: taxableAmount,
location: input.shippingAddress,
category: 'physical',
})
// Calculate shipping
const { cost: shippingCost } = await api.functions.execute('calculate-shipping', {
items: input.items,
destination: input.shippingAddress,
})
// Calculate total
const total = taxableAmount + taxAmount + shippingCost
return {
subtotal,
discounts: {
customer: customerDiscount,
promo: promoDiscount,
total: customerDiscount + promoDiscount,
},
taxAmount,
shippingCost,
total,
savings: customerDiscount + promoDiscount,
}
},
})
// Use it
const pricing = await api.functions.execute(priceCalculator.id, {
items: [
{ productId: 'prod-1', quantity: 2, unitPrice: 29.99 },
{ productId: 'prod-2', quantity: 1, unitPrice: 49.99 },
],
customerId: 'cust-123',
promoCode: 'SAVE10',
shippingAddress: {
country: 'US',
state: 'CA',
zipCode: '94102',
},
})SaaS: Usage Calculator
const usageCalculator = await api.functions.create({
name: 'SaaS Usage Calculator',
description: 'Calculate monthly bill based on usage metrics',
execute: async (input) => {
const { customerId, period } = input
// Get customer plan
const customer = await api.resources.get('Customer', customerId)
const plan = await api.resources.get('Plan', customer.planId)
// Get usage metrics
const usage = await api.metrics.list({
customerId,
period,
})
// Calculate base fee
const baseFee = plan.basePrice
// Calculate usage fees
const usageFees = plan.metrics.map((metric) => {
const used = usage.find((u) => u.name === metric.name)?.value || 0
const included = metric.included || 0
const overage = Math.max(0, used - included)
return {
metric: metric.name,
used,
included,
overage,
fee: overage * metric.pricePerUnit,
}
})
const totalUsageFees = usageFees.reduce((sum, u) => sum + u.fee, 0)
// Apply any credits
const credits = customer.credits || 0
const subtotal = baseFee + totalUsageFees
const creditsApplied = Math.min(credits, subtotal)
const total = subtotal - creditsApplied
return {
period,
baseFee,
usageFees,
subtotal,
creditsApplied,
total,
nextBillingDate: calculateNextBillingDate(period),
}
},
})Next Steps
- Workflows API - Compose functions into workflows
- Agents API - Deploy autonomous agents that execute functions
- Actions API - Execute functions as semantic actions
- Error Handling - Learn about error handling patterns