feat(brex): add transfer/budget/spend-limit/vendor write tools (#5558)

* feat(brex): add transfer/budget/spend-limit/vendor write tools

- fix get_company transformResponse reading camelCase accountType instead of Brex's account_type field (always null in prod)
- add brex_create_transfer, brex_create_budget, brex_archive_budget, brex_create_spend_limit, brex_create_vendor, brex_update_vendor
- endpoints verified against Brex's live payments_api.yaml and budgets_api.yaml OpenAPI specs
- guard required money-amount fields against blank/NaN input instead of silently coercing to 0

* fix(brex): correct status enum docs and normalize expense date filters

- get_budget/get_spend_limit output descriptions listed status enum values not in Brex's actual schema
- list_expenses now normalizes purchased_at_start/end through toBrexDateTime for consistency with list_card_transactions/list_cash_transactions

* fix(brex): preserve zero values and normalize booleans in write params

- toRequiredAmount now rejects whitespace-only input (Number(' ') coerces to 0)
- limitBufferPercentage/transactionLimitAmount used truthy checks that dropped explicit 0
- isPproEnabled now normalized to a real boolean instead of forwarding a stringified 'false' from dynamic references

* fix(brex): null-safe PPRO coercion and fail-fast on empty vendor update

- toOptionalBoolean now treats null the same as undefined (was only checking undefined), so a null isPproEnabled from a dynamic reference is omitted instead of coerced to false
- brex_update_vendor throws when no updatable field (companyName/email/phone) is provided, instead of sending an empty PUT body
This commit is contained in:
Waleed
2026-07-09 22:54:46 -07:00
committed by GitHub
parent f9a5d8b113
commit 2cfa040f23
14 changed files with 1531 additions and 11 deletions
+510 -7
View File
@@ -4,6 +4,35 @@ import { AuthMode, IntegrationType } from '@/blocks/types'
import { normalizeFileInput } from '@/blocks/utils'
import type { BrexResponse } from '@/tools/brex/types'
/** Coerces a required money-amount field to a finite number, throwing on blank/non-numeric input rather than silently sending 0 or NaN to Brex. */
function toRequiredAmount(value: unknown, fieldLabel: string): number {
if (value == null || (typeof value === 'string' && value.trim() === '')) {
throw new Error(`${fieldLabel} must be a valid number`)
}
const parsed = Number(value)
if (!Number.isFinite(parsed)) {
throw new Error(`${fieldLabel} must be a valid number`)
}
return parsed
}
/** Coerces an optional numeric field to a finite number, throwing on non-numeric input instead of silently forwarding NaN. Preserves explicit 0. */
function toOptionalFiniteNumber(value: unknown, fieldLabel: string): number | undefined {
if (value == null || (typeof value === 'string' && value.trim() === '')) return undefined
const parsed = Number(value)
if (!Number.isFinite(parsed)) {
throw new Error(`${fieldLabel} must be a valid number`)
}
return parsed
}
/** Normalizes a boolean field that may arrive as a string (e.g. from a dynamic <Block.output> reference) instead of an actual boolean. */
function toOptionalBoolean(value: unknown): boolean | undefined {
if (value == null) return undefined
if (typeof value === 'boolean') return value
return String(value).toLowerCase() === 'true'
}
const PAGINATED_OPERATIONS = new Set([
'list_expenses',
'list_card_transactions',
@@ -66,13 +95,19 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
// Budgets
{ label: 'List Budgets', id: 'list_budgets' },
{ label: 'Get Budget', id: 'get_budget' },
{ label: 'Create Budget', id: 'create_budget' },
{ label: 'Archive Budget', id: 'archive_budget' },
{ label: 'List Spend Limits', id: 'list_spend_limits' },
{ label: 'Get Spend Limit', id: 'get_spend_limit' },
{ label: 'Create Spend Limit', id: 'create_spend_limit' },
// Payments
{ label: 'List Vendors', id: 'list_vendors' },
{ label: 'Get Vendor', id: 'get_vendor' },
{ label: 'Create Vendor', id: 'create_vendor' },
{ label: 'Update Vendor', id: 'update_vendor' },
{ label: 'List Transfers', id: 'list_transfers' },
{ label: 'Get Transfer', id: 'get_transfer' },
{ label: 'Create Transfer', id: 'create_transfer' },
],
value: () => 'list_expenses',
},
@@ -142,11 +177,16 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
placeholder: 'ID of the cash account (Get Cash Account defaults to primary)',
condition: {
field: 'operation',
value: ['list_cash_transactions', 'list_cash_statements', 'get_cash_account'],
value: [
'list_cash_transactions',
'list_cash_statements',
'get_cash_account',
'create_transfer',
],
},
required: {
field: 'operation',
value: ['list_cash_transactions', 'list_cash_statements'],
value: ['list_cash_transactions', 'list_cash_statements', 'create_transfer'],
},
},
{
@@ -162,8 +202,8 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
title: 'Budget ID',
type: 'short-input',
placeholder: 'ID of the budget',
condition: { field: 'operation', value: 'get_budget' },
required: { field: 'operation', value: 'get_budget' },
condition: { field: 'operation', value: ['get_budget', 'archive_budget'] },
required: { field: 'operation', value: ['get_budget', 'archive_budget'] },
},
{
id: 'spendLimitId',
@@ -178,8 +218,8 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
title: 'Vendor ID',
type: 'short-input',
placeholder: 'ID of the vendor',
condition: { field: 'operation', value: 'get_vendor' },
required: { field: 'operation', value: 'get_vendor' },
condition: { field: 'operation', value: ['get_vendor', 'update_vendor'] },
required: { field: 'operation', value: ['get_vendor', 'update_vendor'] },
},
{
id: 'transferId',
@@ -189,6 +229,305 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
condition: { field: 'operation', value: 'get_transfer' },
required: { field: 'operation', value: 'get_transfer' },
},
{
id: 'companyName',
title: 'Company Name',
type: 'short-input',
placeholder: 'Name for the vendor (must be unique)',
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
required: { field: 'operation', value: 'create_vendor' },
},
{
id: 'vendorEmail',
title: 'Email',
type: 'short-input',
placeholder: 'Email address for the vendor',
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
},
{
id: 'vendorPhone',
title: 'Phone',
type: 'short-input',
placeholder: 'Phone number for the vendor',
condition: { field: 'operation', value: ['create_vendor', 'update_vendor'] },
},
{
id: 'vendorPaymentInstrumentId',
title: 'Vendor Payment Instrument ID',
type: 'short-input',
placeholder:
"ID of the vendor's payment instrument to pay (from the vendor's payment accounts)",
condition: { field: 'operation', value: 'create_transfer' },
required: { field: 'operation', value: 'create_transfer' },
},
{
id: 'amount',
title: 'Amount',
type: 'short-input',
placeholder: 'Amount in the smallest unit of the currency (e.g., cents for USD)',
condition: { field: 'operation', value: ['create_transfer', 'create_budget'] },
required: { field: 'operation', value: ['create_transfer', 'create_budget'] },
},
{
id: 'currency',
title: 'Currency',
type: 'short-input',
placeholder: 'ISO 4217 currency code (defaults to USD)',
mode: 'advanced',
condition: {
field: 'operation',
value: ['create_transfer', 'create_budget', 'create_spend_limit'],
},
},
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Description of the transfer, budget, or spend limit',
condition: {
field: 'operation',
value: ['create_transfer', 'create_budget', 'create_spend_limit'],
},
required: { field: 'operation', value: ['create_transfer', 'create_budget'] },
},
{
id: 'externalMemo',
title: 'External Memo',
type: 'short-input',
placeholder: 'Memo shown to the recipient (max 90 chars for ACH/Wire, 40 for Cheque)',
condition: { field: 'operation', value: 'create_transfer' },
required: { field: 'operation', value: 'create_transfer' },
},
{
id: 'approvalType',
title: 'Approval Type',
type: 'dropdown',
options: [{ label: 'Manual approval required', id: 'MANUAL' }],
placeholder: 'Default policy applies if left blank',
mode: 'advanced',
condition: { field: 'operation', value: 'create_transfer' },
},
{
id: 'isPproEnabled',
title: 'Enable Principal Protection (PPRO)',
type: 'switch',
mode: 'advanced',
condition: { field: 'operation', value: 'create_transfer' },
},
{
id: 'resourceName',
title: 'Name',
type: 'short-input',
placeholder: 'Name for the budget or spend limit',
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
required: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
},
{
id: 'parentBudgetId',
title: 'Parent Budget ID',
type: 'short-input',
placeholder: 'ID of the parent budget',
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
required: { field: 'operation', value: 'create_budget' },
},
{
id: 'periodRecurrenceType',
title: 'Period Recurrence',
type: 'dropdown',
options: [
{ label: 'Weekly', id: 'WEEKLY' },
{ label: 'Monthly', id: 'MONTHLY' },
{ label: 'Quarterly', id: 'QUARTERLY' },
{ label: 'Yearly', id: 'YEARLY' },
{ label: 'One-time', id: 'ONE_TIME' },
],
condition: { field: 'operation', value: 'create_budget' },
required: { field: 'operation', value: 'create_budget' },
},
{
id: 'startDate',
title: 'Start Date',
type: 'short-input',
placeholder: 'Date the budget/spend limit should start counting (YYYY-MM-DD)',
mode: 'advanced',
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
},
{
id: 'endDate',
title: 'End Date',
type: 'short-input',
placeholder: 'Date the budget/spend limit should stop counting (YYYY-MM-DD)',
mode: 'advanced',
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
},
{
id: 'ownerUserIds',
title: 'Owner User IDs',
type: 'short-input',
placeholder: 'Comma-separated user IDs of the budget/spend limit owners',
mode: 'advanced',
condition: { field: 'operation', value: ['create_budget', 'create_spend_limit'] },
wandConfig: {
enabled: true,
prompt:
'Generate a comma-separated list of Brex user IDs to set as owners based on the description.\n\nReturn ONLY the comma-separated user IDs - no explanations, no extra text.',
placeholder: 'Describe which users should own this budget/spend limit...',
},
},
{
id: 'spendLimitPeriodRecurrenceType',
title: 'Period Recurrence',
type: 'dropdown',
options: [
{ label: 'Per week', id: 'PER_WEEK' },
{ label: 'Per month', id: 'PER_MONTH' },
{ label: 'Per quarter', id: 'PER_QUARTER' },
{ label: 'Per year', id: 'PER_YEAR' },
{ label: 'One-time', id: 'ONE_TIME' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'spendType',
title: 'Spend Type',
type: 'dropdown',
options: [
{ label: 'Budget-provisioned cards only', id: 'BUDGET_PROVISIONED_CARDS_ONLY' },
{
label: 'Non-budget-provisioned cards allowed',
id: 'NON_BUDGET_PROVISIONED_CARDS_ALLOWED',
},
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'expenseVisibility',
title: 'Expense Visibility',
type: 'dropdown',
options: [
{ label: 'Shared with all members', id: 'SHARED' },
{ label: 'Private', id: 'PRIVATE' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'authorizationVisibility',
title: 'Authorization Visibility',
type: 'dropdown',
options: [
{ label: 'Public to all members', id: 'PUBLIC' },
{ label: 'Private to controllers/owners', id: 'PRIVATE' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'limitIncreaseSetting',
title: 'Limit Increase Requests',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'ENABLED' },
{ label: 'Disabled', id: 'DISABLED' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'autoTransferCardsSetting',
title: 'Auto Transfer Cards',
type: 'dropdown',
options: [
{ label: 'Disabled', id: 'DISABLED' },
{ label: 'Enabled', id: 'ENABLED' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'autoCreateLimitCardsSetting',
title: 'Auto Create Limit Cards',
type: 'dropdown',
options: [
{ label: 'Disabled', id: 'DISABLED' },
{ label: 'All members', id: 'ALL_MEMBERS' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'expensePolicyId',
title: 'Expense Policy ID',
type: 'short-input',
placeholder: 'ID of the expense policy for this spend limit',
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'baseLimitAmount',
title: 'Base Limit Amount',
type: 'short-input',
placeholder: 'Base limit amount in the smallest unit of the currency (e.g., cents for USD)',
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'authorizationType',
title: 'Authorization Type',
type: 'dropdown',
options: [
{ label: 'Hard (declines over available balance)', id: 'HARD' },
{ label: 'Soft', id: 'SOFT' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'rolloverRefreshRate',
title: 'Rollover Refresh Rate',
type: 'dropdown',
options: [
{ label: 'Off', id: 'OFF' },
{ label: 'Never', id: 'NEVER' },
{ label: 'Per month', id: 'PER_MONTH' },
{ label: 'Per quarter', id: 'PER_QUARTER' },
{ label: 'Per year', id: 'PER_YEAR' },
],
condition: { field: 'operation', value: 'create_spend_limit' },
required: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'limitBufferPercentage',
title: 'Limit Buffer Percentage',
type: 'short-input',
placeholder: 'Flexible buffer on the limit as a 0-100 percentage',
mode: 'advanced',
condition: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'transactionLimitAmount',
title: 'Transaction Limit Amount',
type: 'short-input',
placeholder: 'Per-transaction limit in the smallest unit of the currency',
mode: 'advanced',
condition: { field: 'operation', value: 'create_spend_limit' },
},
{
id: 'spendLimitMemberUserIds',
title: 'Member User IDs',
type: 'short-input',
placeholder: 'Comma-separated user IDs of the spend limit members',
mode: 'advanced',
condition: { field: 'operation', value: 'create_spend_limit' },
wandConfig: {
enabled: true,
prompt:
'Generate a comma-separated list of Brex user IDs to set as spend limit members based on the description.\n\nReturn ONLY the comma-separated user IDs - no explanations, no extra text.',
placeholder: 'Describe which users should be members of this spend limit...',
},
},
{
id: 'email',
title: 'Email',
@@ -388,12 +727,18 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
'brex_get_company',
'brex_list_budgets',
'brex_get_budget',
'brex_create_budget',
'brex_archive_budget',
'brex_list_spend_limits',
'brex_get_spend_limit',
'brex_create_spend_limit',
'brex_list_vendors',
'brex_get_vendor',
'brex_create_vendor',
'brex_update_vendor',
'brex_list_transfers',
'brex_get_transfer',
'brex_create_transfer',
],
config: {
tool: (params) => `brex_${params.operation}`,
@@ -459,15 +804,88 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
case 'get_budget':
result.budgetId = params.budgetId
break
case 'create_budget': {
result.name = params.resourceName
result.description = params.description
result.parentBudgetId = params.parentBudgetId
result.periodRecurrenceType = params.periodRecurrenceType
result.amount = toRequiredAmount(params.amount, 'Amount')
if (params.currency) result.currency = params.currency
if (params.ownerUserIds) result.ownerUserIds = params.ownerUserIds
if (params.startDate) result.startDate = params.startDate
if (params.endDate) result.endDate = params.endDate
break
}
case 'archive_budget':
result.budgetId = params.budgetId
break
case 'get_spend_limit':
result.spendLimitId = params.spendLimitId
break
case 'create_spend_limit': {
result.name = params.resourceName
result.periodRecurrenceType = params.spendLimitPeriodRecurrenceType
result.spendType = params.spendType
result.expenseVisibility = params.expenseVisibility
result.authorizationVisibility = params.authorizationVisibility
result.limitIncreaseSetting = params.limitIncreaseSetting
result.autoTransferCardsSetting = params.autoTransferCardsSetting
result.autoCreateLimitCardsSetting = params.autoCreateLimitCardsSetting
result.expensePolicyId = params.expensePolicyId
result.baseLimitAmount = toRequiredAmount(params.baseLimitAmount, 'Base limit amount')
if (params.currency) result.currency = params.currency
result.authorizationType = params.authorizationType
result.rolloverRefreshRate = params.rolloverRefreshRate
const limitBufferPercentage = toOptionalFiniteNumber(
params.limitBufferPercentage,
'Limit buffer percentage'
)
if (limitBufferPercentage !== undefined)
result.limitBufferPercentage = limitBufferPercentage
if (params.description) result.description = params.description
if (params.parentBudgetId) result.parentBudgetId = params.parentBudgetId
if (params.startDate) result.startDate = params.startDate
if (params.endDate) result.endDate = params.endDate
const transactionLimitAmount = toOptionalFiniteNumber(
params.transactionLimitAmount,
'Transaction limit amount'
)
if (transactionLimitAmount !== undefined)
result.transactionLimitAmount = transactionLimitAmount
if (params.ownerUserIds) result.ownerUserIds = params.ownerUserIds
if (params.spendLimitMemberUserIds)
result.memberUserIds = params.spendLimitMemberUserIds
break
}
case 'get_vendor':
result.vendorId = params.vendorId
break
case 'create_vendor':
result.companyName = params.companyName
if (params.vendorEmail) result.email = params.vendorEmail
if (params.vendorPhone) result.phone = params.vendorPhone
break
case 'update_vendor':
result.vendorId = params.vendorId
if (params.companyName) result.companyName = params.companyName
if (params.vendorEmail) result.email = params.vendorEmail
if (params.vendorPhone) result.phone = params.vendorPhone
break
case 'get_transfer':
result.transferId = params.transferId
break
case 'create_transfer': {
result.cashAccountId = params.accountId
result.vendorPaymentInstrumentId = params.vendorPaymentInstrumentId
result.amount = toRequiredAmount(params.amount, 'Amount')
if (params.currency) result.currency = params.currency
result.description = params.description
result.externalMemo = params.externalMemo
if (params.approvalType) result.approvalType = params.approvalType
const pproEnabled = toOptionalBoolean(params.isPproEnabled)
if (pproEnabled !== undefined) result.isPproEnabled = pproEnabled
break
}
default:
break
}
@@ -508,6 +926,76 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
},
cursor: { type: 'string', description: 'Pagination cursor' },
limit: { type: 'string', description: 'Number of results to return' },
companyName: { type: 'string', description: 'Vendor company name' },
vendorEmail: { type: 'string', description: 'Vendor email address' },
vendorPhone: { type: 'string', description: 'Vendor phone number' },
vendorPaymentInstrumentId: {
type: 'string',
description: "Vendor's payment instrument ID for the transfer counterparty",
},
amount: { type: 'string', description: 'Amount in the smallest unit of the currency' },
currency: { type: 'string', description: 'ISO 4217 currency code' },
description: {
type: 'string',
description: 'Description of the transfer, budget, or spend limit',
},
externalMemo: { type: 'string', description: 'External memo shown to the transfer recipient' },
approvalType: { type: 'string', description: 'Transfer approval type (MANUAL)' },
isPproEnabled: {
type: 'boolean',
description: 'Whether to enable Principal Protection (PPRO) on the transfer',
},
resourceName: { type: 'string', description: 'Name for the budget or spend limit' },
parentBudgetId: { type: 'string', description: 'Parent budget ID' },
periodRecurrenceType: {
type: 'string',
description: 'Budget period recurrence (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
},
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
ownerUserIds: { type: 'string', description: 'Comma-separated owner user IDs' },
spendLimitPeriodRecurrenceType: {
type: 'string',
description:
'Spend limit period recurrence (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
},
spendType: { type: 'string', description: 'Spend limit spend type' },
expenseVisibility: {
type: 'string',
description: 'Spend limit expense visibility (SHARED, PRIVATE)',
},
authorizationVisibility: {
type: 'string',
description: 'Spend limit authorization visibility (PUBLIC, PRIVATE)',
},
limitIncreaseSetting: {
type: 'string',
description: 'Whether members can request limit increases',
},
autoTransferCardsSetting: {
type: 'string',
description: 'Auto transfer setting for virtual cards',
},
autoCreateLimitCardsSetting: {
type: 'string',
description: 'Auto limit card creation setting',
},
expensePolicyId: { type: 'string', description: 'Expense policy ID for the spend limit' },
baseLimitAmount: {
type: 'string',
description: 'Base spend limit amount before increases/rollovers',
},
authorizationType: {
type: 'string',
description: 'Spend limit authorization type (HARD, SOFT)',
},
rolloverRefreshRate: { type: 'string', description: 'Spend limit rollover refresh rate' },
limitBufferPercentage: { type: 'string', description: 'Flexible buffer percentage (0-100)' },
transactionLimitAmount: { type: 'string', description: 'Per-transaction limit amount' },
spendLimitMemberUserIds: {
type: 'string',
description: 'Comma-separated member user IDs for a new spend limit',
},
},
outputs: {
items: { type: 'json', description: 'Items returned by list operations' },
@@ -543,7 +1031,7 @@ export const BrexBlock: BlockConfig<BrexResponse> = {
expenseId: { type: 'string', description: 'ID of the expense the receipt was attached to' },
firstName: { type: 'string', description: 'First name of the user' },
lastName: { type: 'string', description: 'Last name of the user' },
email: { type: 'string', description: 'Email address of the user' },
email: { type: 'string', description: 'Email address of the user or vendor' },
managerId: { type: 'string', description: 'Manager ID of the user' },
titleId: { type: 'string', description: 'Title ID of the user' },
legalName: { type: 'string', description: 'Legal name of the company' },
@@ -660,6 +1148,15 @@ export const BrexBlockMeta = {
tags: ['automation'],
alsoIntegrations: ['gmail'],
},
{
icon: BrexIcon,
title: 'Brex approved-invoice payment automation',
prompt:
'Build a workflow that takes an approved invoice (vendor name, amount, and memo), creates the vendor in Brex if it does not already exist, and creates a transfer from the company cash account to pay it.',
modules: ['workflows'],
category: 'operations',
tags: ['automation'],
},
{
icon: BrexIcon,
title: 'Brex team directory assistant',
@@ -717,5 +1214,11 @@ export const BrexBlockMeta = {
content:
'# Statement Reconciliation\n\nTie a card statement back to its underlying transactions.\n\n## Steps\n1. List card statements and pick the period to reconcile.\n2. List card transactions posted within that period using the posted-at filter.\n3. Compare transaction totals to the statement start and end balances and flag gaps.\n\n## Output\nReturn the statement period, its balances, the transaction total for the period, and any discrepancy that needs review.',
},
{
name: 'pay-vendor',
description: 'Pay a vendor from a Brex cash account, creating the vendor first if needed.',
content:
"# Pay a Vendor\n\nSend a payment to a vendor through Brex.\n\n## Steps\n1. List vendors and check if the target vendor already exists by name.\n2. If not, use Create Vendor with the company name (and email/phone if known).\n3. Use the vendor's payment_accounts entry to get the payment_instrument_id, then use Create Transfer with that ID, the cash account to pay from, the amount (in cents), a description, and an external memo.\n4. Confirm the transfer was created and note its status.\n\n## Output\nReturn the vendor used, the transfer ID and status, and the amount sent. Flag anything that requires manual approval (PENDING_APPROVAL status).",
},
],
} as const satisfies BlockMeta
+71
View File
@@ -0,0 +1,71 @@
import type { BrexArchiveBudgetParams, BrexArchiveBudgetResponse } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexArchiveBudgetTool: ToolConfig<BrexArchiveBudgetParams, BrexArchiveBudgetResponse> =
{
id: 'brex_archive_budget',
name: 'Brex Archive Budget',
description:
'Archive a Brex budget, making any spend limits beneath it unusable for future expenses and removing it from the UI',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
budgetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the budget to archive',
},
},
request: {
url: (params) =>
`${BREX_API_BASE}/v2/budgets/${encodeURIComponent(params.budgetId.trim())}/archive`,
method: 'POST',
headers: (params) => buildBrexHeaders(params.apiKey),
},
transformResponse: async (response, params) => {
if (!response.ok) {
// parseBrexJson throws a descriptive error for non-2xx responses; it never
// returns in this branch since the body cannot be a successful JSON payload.
await parseBrexJson(response)
}
// Brex's archive endpoint does not document a response body schema; fall back
// to the request's budget ID and an ARCHIVED status when the body is empty.
let data: Record<string, unknown> = {}
const text = await response.text()
if (text) {
try {
data = JSON.parse(text)
} catch {
data = {}
}
}
return {
success: true,
output: {
budgetId: (data.budget_id as string) ?? params?.budgetId ?? '',
spendBudgetStatus: (data.spend_budget_status as string) ?? 'ARCHIVED',
},
}
},
outputs: {
budgetId: { type: 'string', description: 'ID of the archived budget' },
spendBudgetStatus: {
type: 'string',
description: 'Status of the budget after archiving',
optional: true,
},
},
}
+145
View File
@@ -0,0 +1,145 @@
import { generateId } from '@sim/utils/id'
import type { BrexCreateBudgetParams, BrexCreateBudgetResponse } from '@/tools/brex/types'
import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson, splitBrexIdList } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexCreateBudgetTool: ToolConfig<BrexCreateBudgetParams, BrexCreateBudgetResponse> = {
id: 'brex_create_budget',
name: 'Brex Create Budget',
description: 'Create a new budget in the Brex account',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name for the budget',
},
description: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Description of what the budget is used for',
},
parentBudgetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the parent budget',
},
periodRecurrenceType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Period type of the budget (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
},
amount: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Budget amount, in the smallest unit of the currency (e.g., cents for USD)',
},
currency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 4217 currency code (defaults to USD)',
},
ownerUserIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated user IDs of the budget owners',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date the budget should start counting (YYYY-MM-DD)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date the budget should stop counting (YYYY-MM-DD)',
},
},
request: {
url: () => `${BREX_API_BASE}/v2/budgets`,
method: 'POST',
headers: (params) => ({
...buildBrexHeaders(params.apiKey),
'Idempotency-Key': generateId(),
}),
body: (params) => {
const body: Record<string, unknown> = {
name: params.name,
description: params.description,
parent_budget_id: params.parentBudgetId,
period_recurrence_type: params.periodRecurrenceType,
amount: {
amount: params.amount,
currency: params.currency || 'USD',
},
}
const ownerUserIds = splitBrexIdList(params.ownerUserIds)
if (ownerUserIds) body.owner_user_ids = ownerUserIds
if (params.startDate) body.start_date = params.startDate
if (params.endDate) body.end_date = params.endDate
return body
},
},
transformResponse: async (response) => {
const data = await parseBrexJson(response)
return {
success: true,
output: {
budgetId: data.budget_id ?? '',
accountId: data.account_id ?? '',
name: data.name ?? '',
description: data.description ?? null,
parentBudgetId: data.parent_budget_id ?? null,
ownerUserIds: data.owner_user_ids ?? [],
periodRecurrenceType: data.period_recurrence_type ?? '',
startDate: data.start_date ?? null,
endDate: data.end_date ?? null,
amount: data.amount ?? null,
spendBudgetStatus: data.spend_budget_status ?? '',
limitType: data.limit_type ?? null,
},
}
},
outputs: {
budgetId: { type: 'string', description: 'Unique budget ID' },
accountId: { type: 'string', description: 'Account ID the budget belongs to' },
name: { type: 'string', description: 'Budget name' },
description: { type: 'string', description: 'Budget description', optional: true },
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
ownerUserIds: { type: 'array', description: 'User IDs of the budget owners' },
periodRecurrenceType: {
type: 'string',
description: 'Budget period recurrence (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)',
},
startDate: { type: 'string', description: 'Budget start date', optional: true },
endDate: { type: 'string', description: 'Budget end date', optional: true },
amount: {
type: 'json',
description: 'Budget amount',
optional: true,
properties: BREX_MONEY_PROPERTIES,
},
spendBudgetStatus: { type: 'string', description: 'Status of the created budget' },
limitType: { type: 'string', description: 'Budget limit type', optional: true },
},
}
+256
View File
@@ -0,0 +1,256 @@
import { generateId } from '@sim/utils/id'
import type { BrexCreateSpendLimitParams, BrexCreateSpendLimitResponse } from '@/tools/brex/types'
import { BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson, splitBrexIdList } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexCreateSpendLimitTool: ToolConfig<
BrexCreateSpendLimitParams,
BrexCreateSpendLimitResponse
> = {
id: 'brex_create_spend_limit',
name: 'Brex Create Spend Limit',
description: 'Create a new spend limit (hard-authorization card program) in the Brex account',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name for the spend limit',
},
periodRecurrenceType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Period type of the spend limit (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
},
spendType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Whether the spend limit can only be spent from cards it provisions (BUDGET_PROVISIONED_CARDS_ONLY, NON_BUDGET_PROVISIONED_CARDS_ALLOWED)',
},
expenseVisibility: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Whether expenses on this spend limit are viewable by all members (SHARED, PRIVATE)',
},
authorizationVisibility: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Whether the limit amount is visible to all members, or just controllers/bookkeepers/owners (PUBLIC, PRIVATE)',
},
limitIncreaseSetting: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Whether members can request limit increases (ENABLED, DISABLED)',
},
autoTransferCardsSetting: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'How auto transfer works for virtual cards on this spend limit (DISABLED, ENABLED)',
},
autoCreateLimitCardsSetting: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'How auto limit card creation works for members (DISABLED, ALL_MEMBERS)',
},
expensePolicyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the expense policy corresponding to this spend limit',
},
baseLimitAmount: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description:
'Base spend limit amount, without increases/rollovers, in the smallest unit of the currency (e.g., cents for USD)',
},
currency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 4217 currency code for the base limit (defaults to USD)',
},
authorizationType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Whether authorizations decline based on available balance (HARD, SOFT)',
},
rolloverRefreshRate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Recurrence at which rolled-over unused funds stop rolling over (OFF, NEVER, PER_MONTH, PER_QUARTER, PER_YEAR)',
},
limitBufferPercentage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Flexible buffer on the limit as a 0-100 percentage',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description of what the spend limit is used for',
},
parentBudgetId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of the parent budget',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date the spend limit should start counting (YYYY-MM-DD)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Date the spend limit should expire (YYYY-MM-DD)',
},
transactionLimitAmount: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Per-transaction limit this spend limit enforces, in the smallest unit of the currency',
},
ownerUserIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated user IDs of the spend limit owners',
},
memberUserIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated user IDs of the spend limit members',
},
},
request: {
url: () => `${BREX_API_BASE}/v2/spend_limits`,
method: 'POST',
headers: (params) => ({
...buildBrexHeaders(params.apiKey),
'Idempotency-Key': generateId(),
}),
body: (params) => {
const currency = params.currency || 'USD'
const body: Record<string, unknown> = {
name: params.name,
period_recurrence_type: params.periodRecurrenceType,
spend_type: params.spendType,
expense_visibility: params.expenseVisibility,
authorization_visibility: params.authorizationVisibility,
limit_increase_setting: params.limitIncreaseSetting,
auto_transfer_cards_setting: params.autoTransferCardsSetting,
auto_create_limit_cards_setting: params.autoCreateLimitCardsSetting,
expense_policy_id: params.expensePolicyId,
authorization_settings: {
base_limit: {
amount: params.baseLimitAmount,
currency,
},
authorization_type: params.authorizationType,
rollover_refresh_rate: params.rolloverRefreshRate,
...(params.limitBufferPercentage !== undefined
? { limit_buffer_percentage: params.limitBufferPercentage }
: {}),
},
}
if (params.description) body.description = params.description
if (params.parentBudgetId) body.parent_budget_id = params.parentBudgetId
if (params.startDate) body.start_date = params.startDate
if (params.endDate) body.end_date = params.endDate
if (params.transactionLimitAmount !== undefined) {
body.transaction_limit = { amount: params.transactionLimitAmount, currency }
}
const ownerUserIds = splitBrexIdList(params.ownerUserIds)
if (ownerUserIds) body.owner_user_ids = ownerUserIds
const memberUserIds = splitBrexIdList(params.memberUserIds)
if (memberUserIds) body.member_user_ids = memberUserIds
return body
},
},
transformResponse: async (response) => {
const data = await parseBrexJson(response)
return {
success: true,
output: {
id: data.id ?? '',
accountId: data.account_id ?? '',
name: data.name ?? '',
description: data.description ?? null,
parentBudgetId: data.parent_budget_id ?? null,
status: data.status ?? '',
periodRecurrenceType: data.period_recurrence_type ?? '',
spendType: data.spend_type ?? '',
startDate: data.start_date ?? null,
endDate: data.end_date ?? null,
ownerUserIds: data.owner_user_ids ?? [],
memberUserIds: data.member_user_ids ?? [],
currentPeriodBalance: data.current_period_balance ?? null,
authorizationSettings: data.authorization_settings ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Unique spend limit ID' },
accountId: { type: 'string', description: 'Account ID the spend limit belongs to' },
name: { type: 'string', description: 'Spend limit name' },
description: { type: 'string', description: 'Spend limit description', optional: true },
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
status: { type: 'string', description: 'Spend limit status' },
periodRecurrenceType: {
type: 'string',
description: 'Period recurrence (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)',
},
spendType: { type: 'string', description: 'Spend type of the limit' },
startDate: { type: 'string', description: 'Spend limit start date', optional: true },
endDate: { type: 'string', description: 'Spend limit end date', optional: true },
ownerUserIds: { type: 'array', description: 'User IDs of the spend limit owners' },
memberUserIds: { type: 'array', description: 'User IDs of the spend limit members' },
currentPeriodBalance: {
type: 'json',
description: 'Spend and rollover amounts for the current period',
optional: true,
properties: BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES,
},
authorizationSettings: {
type: 'json',
description: 'Authorization settings (base limit, authorization type, rollover refresh)',
optional: true,
},
},
}
+189
View File
@@ -0,0 +1,189 @@
import { generateId } from '@sim/utils/id'
import type { BrexCreateTransferParams, BrexCreateTransferResponse } from '@/tools/brex/types'
import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexCreateTransferTool: ToolConfig<
BrexCreateTransferParams,
BrexCreateTransferResponse
> = {
id: 'brex_create_transfer',
name: 'Brex Create Transfer',
description: 'Create a money transfer from a Brex cash account to a vendor',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
cashAccountId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'ID of the Brex cash account to send the transfer from (found via the /accounts endpoint)',
},
vendorPaymentInstrumentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
"ID of the vendor's payment instrument to send the transfer to (from the vendor's payment_accounts)",
},
amount: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Amount to transfer, in the smallest unit of the currency (e.g., cents for USD)',
},
currency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 4217 currency code (defaults to USD)',
},
description: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Description of the transfer for internal use (not exposed externally)',
},
externalMemo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'External memo shown to the recipient (max 90 characters for ACH/Wire, 40 for Cheque)',
},
approvalType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Set to MANUAL to require cash admin approval before the transfer is sent',
},
isPproEnabled: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Enable Principal Protection (PPRO) to have Brex cover intermediary/receiving bank fees (international wires only)',
},
},
request: {
url: () => `${BREX_API_BASE}/v1/transfers`,
method: 'POST',
headers: (params) => ({
...buildBrexHeaders(params.apiKey),
// Brex requires a fresh Idempotency-Key per transfer creation to prevent duplicate money movement.
'Idempotency-Key': generateId(),
}),
body: (params) => {
const body: Record<string, unknown> = {
counterparty: {
type: 'VENDOR',
payment_instrument_id: params.vendorPaymentInstrumentId,
},
amount: {
amount: params.amount,
currency: params.currency || 'USD',
},
description: params.description,
external_memo: params.externalMemo,
originating_account: {
type: 'BREX_CASH',
id: params.cashAccountId,
},
}
if (params.approvalType) body.approval_type = params.approvalType
if (params.isPproEnabled !== undefined) body.is_ppro_enabled = params.isPproEnabled
return body
},
},
transformResponse: async (response) => {
const data = await parseBrexJson(response)
return {
success: true,
output: {
id: data.id ?? '',
counterparty: data.counterparty ?? null,
description: data.description ?? null,
paymentType: data.payment_type ?? '',
amount: data.amount ?? null,
processDate: data.process_date ?? null,
originatingAccount: data.originating_account ?? null,
status: data.status ?? '',
cancellationReason: data.cancellation_reason ?? null,
estimatedDeliveryDate: data.estimated_delivery_date ?? null,
creatorUserId: data.creator_user_id ?? null,
createdAt: data.created_at ?? null,
displayName: data.display_name ?? null,
externalMemo: data.external_memo ?? null,
isPproEnabled: data.is_ppro_enabled ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Unique transfer ID' },
counterparty: { type: 'json', description: 'Transfer counterparty details', optional: true },
description: { type: 'string', description: 'Description of the transfer', optional: true },
paymentType: {
type: 'string',
description:
'Payment type (ACH, DOMESTIC_WIRE, CHEQUE, INTERNATIONAL_WIRE, BOOK_TRANSFER, STABLECOIN)',
},
amount: {
type: 'json',
description: 'Transfer amount',
optional: true,
properties: BREX_MONEY_PROPERTIES,
},
processDate: { type: 'string', description: 'Transaction processing date', optional: true },
originatingAccount: {
type: 'json',
description: 'Originating account details for the transfer',
optional: true,
},
status: {
type: 'string',
description: 'Transfer status (PROCESSING, SCHEDULED, PENDING_APPROVAL, FAILED, PROCESSED)',
},
cancellationReason: {
type: 'string',
description: 'Reason the transfer was canceled',
optional: true,
},
estimatedDeliveryDate: {
type: 'string',
description: 'Estimated delivery date for the transfer',
optional: true,
},
creatorUserId: {
type: 'string',
description: 'ID of the user who created the transfer',
optional: true,
},
createdAt: {
type: 'string',
description: 'Creation timestamp of the transfer',
optional: true,
},
displayName: {
type: 'string',
description: 'Human-readable name of the transfer',
optional: true,
},
externalMemo: { type: 'string', description: 'External memo of the transfer', optional: true },
isPproEnabled: {
type: 'boolean',
description: 'Whether Principal Protection (PPRO) is enabled for the transfer',
optional: true,
},
},
}
+75
View File
@@ -0,0 +1,75 @@
import { generateId } from '@sim/utils/id'
import type { BrexCreateVendorParams, BrexCreateVendorResponse } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexCreateVendorTool: ToolConfig<BrexCreateVendorParams, BrexCreateVendorResponse> = {
id: 'brex_create_vendor',
name: 'Brex Create Vendor',
description: 'Create a new vendor in the Brex account',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
companyName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name for the vendor (must be unique)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address for the vendor',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone number for the vendor',
},
},
request: {
url: () => `${BREX_API_BASE}/v1/vendors`,
method: 'POST',
headers: (params) => ({
...buildBrexHeaders(params.apiKey),
'Idempotency-Key': generateId(),
}),
body: (params) => {
const body: Record<string, unknown> = { company_name: params.companyName }
if (params.email) body.email = params.email
if (params.phone) body.phone = params.phone
return body
},
},
transformResponse: async (response) => {
const data = await parseBrexJson(response)
return {
success: true,
output: {
id: data.id ?? '',
companyName: data.company_name ?? null,
email: data.email ?? null,
phone: data.phone ?? null,
paymentAccounts: data.payment_accounts ?? [],
},
}
},
outputs: {
id: { type: 'string', description: 'Unique vendor ID' },
companyName: { type: 'string', description: 'Vendor company name', optional: true },
email: { type: 'string', description: 'Vendor email address', optional: true },
phone: { type: 'string', description: 'Vendor phone number', optional: true },
paymentAccounts: { type: 'array', description: 'Payment accounts associated with the vendor' },
},
}
+1 -1
View File
@@ -72,7 +72,7 @@ export const brexGetBudgetTool: ToolConfig<BrexGetBudgetParams, BrexGetBudgetRes
},
spendBudgetStatus: {
type: 'string',
description: 'Budget status (ACTIVE, ARCHIVED, DELETED, EXPIRED)',
description: 'Budget status (ACTIVE, ARCHIVED, DELETED)',
},
limitType: { type: 'string', description: 'Budget limit type (HARD or SOFT)', optional: true },
},
+1 -1
View File
@@ -63,7 +63,7 @@ export const brexGetSpendLimitTool: ToolConfig<BrexGetSpendLimitParams, BrexGetS
parentBudgetId: { type: 'string', description: 'Parent budget ID', optional: true },
status: {
type: 'string',
description: 'Spend limit status (ACTIVE, EXPIRED, ARCHIVED, DELETED)',
description: 'Spend limit status (ACTIVE, EXPIRED, ARCHIVED)',
},
periodRecurrenceType: {
type: 'string',
+6
View File
@@ -1,3 +1,8 @@
export { brexArchiveBudgetTool } from '@/tools/brex/archive_budget'
export { brexCreateBudgetTool } from '@/tools/brex/create_budget'
export { brexCreateSpendLimitTool } from '@/tools/brex/create_spend_limit'
export { brexCreateTransferTool } from '@/tools/brex/create_transfer'
export { brexCreateVendorTool } from '@/tools/brex/create_vendor'
export { brexGetBudgetTool } from '@/tools/brex/get_budget'
export { brexGetCashAccountTool } from '@/tools/brex/get_cash_account'
export { brexGetCompanyTool } from '@/tools/brex/get_company'
@@ -25,4 +30,5 @@ export { brexListUsersTool } from '@/tools/brex/list_users'
export { brexListVendorsTool } from '@/tools/brex/list_vendors'
export { brexMatchReceiptTool } from '@/tools/brex/match_receipt'
export { brexUpdateExpenseTool } from '@/tools/brex/update_expense'
export { brexUpdateVendorTool } from '@/tools/brex/update_vendor'
export { brexUploadReceiptTool } from '@/tools/brex/upload_receipt'
+5 -2
View File
@@ -6,6 +6,7 @@ import {
BREX_API_BASE,
buildBrexHeaders,
parseBrexJson,
toBrexDateTime,
} from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
@@ -87,8 +88,10 @@ export const brexListExpensesTool: ToolConfig<BrexListExpensesParams, BrexListEx
appendBrexArrayParam(query, 'user_id[]', params.userIds)
appendBrexArrayParam(query, 'status[]', params.statuses)
appendBrexArrayParam(query, 'payment_status[]', params.paymentStatuses)
if (params.purchasedAtStart) query.append('purchased_at_start', params.purchasedAtStart)
if (params.purchasedAtEnd) query.append('purchased_at_end', params.purchasedAtEnd)
if (params.purchasedAtStart)
query.append('purchased_at_start', toBrexDateTime(params.purchasedAtStart))
if (params.purchasedAtEnd)
query.append('purchased_at_end', toBrexDateTime(params.purchasedAtEnd))
appendBrexPagination(query, params)
return `${BREX_API_BASE}/v1/expenses?${query.toString()}`
},
+159
View File
@@ -299,6 +299,76 @@ export interface BrexGetTransferParams {
transferId: string
}
export interface BrexCreateTransferParams {
apiKey: string
cashAccountId: string
vendorPaymentInstrumentId: string
amount: number
currency?: string
description: string
externalMemo: string
approvalType?: string
isPproEnabled?: boolean
}
export interface BrexCreateBudgetParams {
apiKey: string
name: string
description: string
parentBudgetId: string
periodRecurrenceType: string
amount: number
currency?: string
ownerUserIds?: string
startDate?: string
endDate?: string
}
export interface BrexArchiveBudgetParams {
apiKey: string
budgetId: string
}
export interface BrexCreateSpendLimitParams {
apiKey: string
name: string
periodRecurrenceType: string
spendType: string
expenseVisibility: string
authorizationVisibility: string
limitIncreaseSetting: string
autoTransferCardsSetting: string
autoCreateLimitCardsSetting: string
expensePolicyId: string
baseLimitAmount: number
currency?: string
authorizationType: string
rolloverRefreshRate: string
limitBufferPercentage?: number
description?: string
parentBudgetId?: string
startDate?: string
endDate?: string
transactionLimitAmount?: number
ownerUserIds?: string
memberUserIds?: string
}
export interface BrexCreateVendorParams {
apiKey: string
companyName: string
email?: string
phone?: string
}
export interface BrexUpdateVendorParams {
apiKey: string
vendorId: string
companyName?: string
email?: string
phone?: string
}
export interface BrexListExpensesResponse extends ToolResponse {
output: {
items: BrexExpense[]
@@ -559,6 +629,89 @@ export interface BrexGetTransferResponse extends ToolResponse {
}
}
export interface BrexCreateTransferResponse extends ToolResponse {
output: {
id: string
counterparty: Record<string, unknown> | null
description: string | null
paymentType: string
amount: BrexMoney | null
processDate: string | null
originatingAccount: Record<string, unknown> | null
status: string
cancellationReason: string | null
estimatedDeliveryDate: string | null
creatorUserId: string | null
createdAt: string | null
displayName: string | null
externalMemo: string | null
isPproEnabled: boolean | null
}
}
export interface BrexCreateBudgetResponse extends ToolResponse {
output: {
budgetId: string
accountId: string
name: string
description: string | null
parentBudgetId: string | null
ownerUserIds: string[]
periodRecurrenceType: string
startDate: string | null
endDate: string | null
amount: BrexMoney | null
spendBudgetStatus: string
limitType: string | null
}
}
export interface BrexArchiveBudgetResponse extends ToolResponse {
output: {
budgetId: string
spendBudgetStatus: string | null
}
}
export interface BrexCreateSpendLimitResponse extends ToolResponse {
output: {
id: string
accountId: string
name: string
description: string | null
parentBudgetId: string | null
status: string
periodRecurrenceType: string
spendType: string
startDate: string | null
endDate: string | null
ownerUserIds: string[]
memberUserIds: string[]
currentPeriodBalance: BrexSpendLimitPeriodBalance | null
authorizationSettings: Record<string, unknown> | null
}
}
export interface BrexCreateVendorResponse extends ToolResponse {
output: {
id: string
companyName: string | null
email: string | null
phone: string | null
paymentAccounts: unknown[]
}
}
export interface BrexUpdateVendorResponse extends ToolResponse {
output: {
id: string
companyName: string | null
email: string | null
phone: string | null
paymentAccounts: unknown[]
}
}
export type BrexResponse =
| BrexListExpensesResponse
| BrexGetExpenseResponse
@@ -585,6 +738,12 @@ export type BrexResponse =
| BrexGetSpendLimitResponse
| BrexGetVendorResponse
| BrexGetTransferResponse
| BrexCreateTransferResponse
| BrexCreateBudgetResponse
| BrexArchiveBudgetResponse
| BrexCreateSpendLimitResponse
| BrexCreateVendorResponse
| BrexUpdateVendorResponse
export const BREX_MONEY_PROPERTIES: Record<string, OutputProperty> = {
amount: {
+88
View File
@@ -0,0 +1,88 @@
import { generateId } from '@sim/utils/id'
import type { BrexUpdateVendorParams, BrexUpdateVendorResponse } from '@/tools/brex/types'
import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils'
import type { ToolConfig } from '@/tools/types'
export const brexUpdateVendorTool: ToolConfig<BrexUpdateVendorParams, BrexUpdateVendorResponse> = {
id: 'brex_update_vendor',
name: 'Brex Update Vendor',
description: 'Update an existing vendor in the Brex account',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Brex user token (generated from Developer Settings in the Brex dashboard)',
},
vendorId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the vendor to update',
},
companyName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New name for the vendor',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New email address for the vendor',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New phone number for the vendor',
},
},
request: {
url: (params) => `${BREX_API_BASE}/v1/vendors/${encodeURIComponent(params.vendorId.trim())}`,
method: 'PUT',
headers: (params) => ({
...buildBrexHeaders(params.apiKey),
// Optional per Brex's spec for this endpoint, but included for safe-retry semantics.
'Idempotency-Key': generateId(),
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.companyName) body.company_name = params.companyName
if (params.email) body.email = params.email
if (params.phone) body.phone = params.phone
if (Object.keys(body).length === 0) {
throw new Error(
'At least one of company name, email, or phone must be provided to update the vendor'
)
}
return body
},
},
transformResponse: async (response) => {
const data = await parseBrexJson(response)
return {
success: true,
output: {
id: data.id ?? '',
companyName: data.company_name ?? null,
email: data.email ?? null,
phone: data.phone ?? null,
paymentAccounts: data.payment_accounts ?? [],
},
}
},
outputs: {
id: { type: 'string', description: 'Unique vendor ID' },
companyName: { type: 'string', description: 'Vendor company name', optional: true },
email: { type: 'string', description: 'Vendor email address', optional: true },
phone: { type: 'string', description: 'Vendor phone number', optional: true },
paymentAccounts: { type: 'array', description: 'Payment accounts associated with the vendor' },
},
}
+13
View File
@@ -51,6 +51,19 @@ export function appendBrexPagination(
if (params.limit) query.append('limit', params.limit)
}
/**
* Splits a comma-separated string of IDs into a trimmed, non-empty array for
* use in a JSON request body (as opposed to repeated query parameters).
*/
export function splitBrexIdList(value?: string): string[] | undefined {
if (!value) return undefined
const ids = value
.split(',')
.map((id) => id.trim())
.filter(Boolean)
return ids.length > 0 ? ids : undefined
}
/**
* Converts a timestamp to the timezone-less date-time form the Brex Transactions
* API requires (e.g., 2026-01-01T00:00:00). Brex rejects timezone-suffixed
+12
View File
@@ -345,6 +345,11 @@ import {
} from '@/tools/box_sign'
import { brandfetchGetBrandTool, brandfetchSearchTool } from '@/tools/brandfetch'
import {
brexArchiveBudgetTool,
brexCreateBudgetTool,
brexCreateSpendLimitTool,
brexCreateTransferTool,
brexCreateVendorTool,
brexGetBudgetTool,
brexGetCashAccountTool,
brexGetCompanyTool,
@@ -372,6 +377,7 @@ import {
brexListVendorsTool,
brexMatchReceiptTool,
brexUpdateExpenseTool,
brexUpdateVendorTool,
brexUploadReceiptTool,
} from '@/tools/brex'
import {
@@ -4632,6 +4638,11 @@ export const tools: Record<string, ToolConfig> = {
athena_stop_query: athenaStopQueryTool,
brandfetch_get_brand: brandfetchGetBrandTool,
brandfetch_search: brandfetchSearchTool,
brex_archive_budget: brexArchiveBudgetTool,
brex_create_budget: brexCreateBudgetTool,
brex_create_spend_limit: brexCreateSpendLimitTool,
brex_create_transfer: brexCreateTransferTool,
brex_create_vendor: brexCreateVendorTool,
brex_get_budget: brexGetBudgetTool,
brex_get_cash_account: brexGetCashAccountTool,
brex_get_company: brexGetCompanyTool,
@@ -4659,6 +4670,7 @@ export const tools: Record<string, ToolConfig> = {
brex_list_vendors: brexListVendorsTool,
brex_match_receipt: brexMatchReceiptTool,
brex_update_expense: brexUpdateExpenseTool,
brex_update_vendor: brexUpdateVendorTool,
brex_upload_receipt: brexUploadReceiptTool,
brightdata_cancel_snapshot: brightDataCancelSnapshotTool,
brightdata_discover: brightDataDiscoverTool,