diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6161069bef..90987f14fd 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2261,6 +2261,17 @@ export function BrandfetchIcon(props: SVGProps) { ) } +export function BrexIcon(props: SVGProps) { + return ( + + + + ) +} + export function BrightDataIcon(props: SVGProps) { return ( = { azure_devops: AzureIcon, box: BoxCompanyIcon, brandfetch: BrandfetchIcon, + brex: BrexIcon, brightdata: BrightDataIcon, browser_use: BrowserUseIcon, calcom: CalComIcon, diff --git a/apps/docs/content/docs/en/integrations/brex.mdx b/apps/docs/content/docs/en/integrations/brex.mdx new file mode 100644 index 0000000000..09b49faf55 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/brex.mdx @@ -0,0 +1,895 @@ +--- +title: Brex +description: Manage expenses, receipts, transactions, and team data in Brex +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Brex](https://www.brex.com/) is the AI-powered spend platform that gives companies corporate cards, expense management, banking, and bill pay in one place. Finance teams use Brex to control spend with budgets and spend limits, automate expense review, and keep every transaction reconciled with receipts and memos. + +With the Brex integration in Sim, your agents can work directly with your company's spend data: + +- **Expenses**: List and filter expenses by status, owner, or purchase date, fetch full expense details (merchant, amounts, receipts), and update expense memos. +- **Receipts**: Upload a receipt file straight onto a specific card expense, or let Brex automatically match an uploaded receipt to the right expense. +- **Transactions and accounts**: Pull settled card transactions, cash account transactions, account balances, and finalized statements for reporting and reconciliation. +- **Budgets and spend limits**: Read budgets and spend limits — including current period balances — to power utilization reports and proactive alerts. +- **Team**: Look up users, departments, locations, titles, and cards to enrich spend data with organizational context. +- **Payments**: Track vendors and money transfers to monitor payment status end to end. + +Authentication uses a Brex user token, which you can generate from **Developer → Settings** in your Brex dashboard. The integration is intentionally read-focused: it never moves money, issues cards, or exposes card numbers. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrates Brex into the workflow. List and update expenses, upload and match receipts, view card and cash transactions, accounts, budgets, spend limits, vendors, transfers, and team data. + + + +## Actions + +### `brex_list_expenses` + +List expenses in the Brex account with optional filters for user, status, payment status, and purchase date range + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `userIds` | string | No | Comma-separated user IDs to filter expenses by owner | +| `statuses` | string | No | Comma-separated expense statuses to filter by: DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED | +| `paymentStatuses` | string | No | Comma-separated payment statuses to filter by: NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED | +| `purchasedAtStart` | string | No | Only include expenses purchased at or after this ISO 8601 timestamp | +| `purchasedAtEnd` | string | No | Only include expenses purchased at or before this ISO 8601 timestamp | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of expenses to return \(max 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Expenses matching the filters | +| ↳ `id` | string | Unique expense ID | +| ↳ `memo` | string | Memo on the expense | +| ↳ `status` | string | Expense status \(DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED\) | +| ↳ `payment_status` | string | Payment status \(NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED\) | +| ↳ `expense_type` | string | Expense type \(CARD, BILLPAY, REIMBURSEMENT, CLAWBACK, UNSET\) | +| ↳ `category` | string | Expense category \(e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES\) | +| ↳ `merchant` | json | Merchant details | +| ↳ `raw_descriptor` | string | Raw merchant descriptor | +| ↳ `mcc` | string | Merchant category code | +| ↳ `country` | string | Merchant country | +| ↳ `user` | json | User who made the expense | +| ↳ `id` | string | User ID | +| ↳ `first_name` | string | First name | +| ↳ `last_name` | string | Last name | +| ↳ `budget` | json | Budget the expense belongs to | +| ↳ `id` | string | Budget ID | +| ↳ `name` | string | Budget name | +| ↳ `department` | json | Department of the expense owner | +| ↳ `id` | string | Department ID | +| ↳ `name` | string | Department name | +| ↳ `location` | json | Location of the expense owner | +| ↳ `id` | string | Location ID | +| ↳ `name` | string | Location name | +| ↳ `original_amount` | json | Original transaction amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `billing_amount` | json | Amount billed to the account | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `purchased_amount` | json | Amount at the time of purchase | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `receipts` | array | Receipts attached to the expense | +| ↳ `id` | string | Receipt ID | +| ↳ `download_uris` | array | Pre-signed receipt download URLs | +| ↳ `purchased_at` | string | Purchase timestamp \(ISO 8601\) | +| ↳ `updated_at` | string | Last update timestamp \(ISO 8601\) | +| ↳ `dashboard_url` | string | Link to the expense in the Brex dashboard | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_expense` + +Get a single Brex expense by its ID, including merchant, user, and receipt details + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `expenseId` | string | Yes | ID of the expense to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique expense ID | +| `memo` | string | Memo on the expense | +| `status` | string | Expense status \(DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED\) | +| `paymentStatus` | string | Payment status \(NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED\) | +| `expenseType` | string | Expense type \(CARD, BILLPAY, REIMBURSEMENT, CLAWBACK, UNSET\) | +| `category` | string | Expense category \(e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES\) | +| `merchantId` | string | Merchant ID | +| `merchant` | json | Merchant details \(raw descriptor, MCC, country\) | +| ↳ `raw_descriptor` | string | Raw merchant descriptor | +| ↳ `mcc` | string | Merchant category code | +| ↳ `country` | string | Merchant country | +| `budgetId` | string | Budget ID | +| `budget` | json | Budget the expense belongs to | +| ↳ `id` | string | Budget ID | +| ↳ `name` | string | Budget name | +| `departmentId` | string | Department ID | +| `department` | json | Department of the expense owner | +| ↳ `id` | string | Department ID | +| ↳ `name` | string | Department name | +| `locationId` | string | Location ID | +| `location` | json | Location of the expense owner | +| ↳ `id` | string | Location ID | +| ↳ `name` | string | Location name | +| `userId` | string | ID of the user who made the expense | +| `user` | json | User who made the expense | +| ↳ `id` | string | User ID | +| ↳ `first_name` | string | First name | +| ↳ `last_name` | string | Last name | +| `originalAmount` | json | Original transaction amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `billingAmount` | json | Amount billed to the account | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `purchasedAmount` | json | Amount at the time of purchase | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `usdEquivalentAmount` | json | USD equivalent amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `purchasedAt` | string | Purchase timestamp \(ISO 8601\) | +| `updatedAt` | string | Last update timestamp \(ISO 8601\) | +| `paymentPostedAt` | string | Timestamp the payment was posted \(ISO 8601\) | +| `receipts` | array | Receipts attached to the expense | +| ↳ `id` | string | Receipt ID | +| ↳ `download_uris` | array | Pre-signed receipt download URLs | +| `dashboardUrl` | string | Link to the expense in the Brex dashboard | + +### `brex_update_expense` + +Update the memo of a Brex card expense + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `expenseId` | string | Yes | ID of the card expense to update | +| `memo` | string | Yes | New memo for the expense | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique expense ID | +| `memo` | string | Updated memo on the expense | +| `status` | string | Expense status \(DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED\) | +| `paymentStatus` | string | Payment status \(NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED\) | +| `category` | string | Expense category \(e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES\) | +| `merchantId` | string | Merchant ID | +| `budgetId` | string | Budget ID | +| `originalAmount` | json | Original transaction amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `billingAmount` | json | Amount billed to the account | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `purchasedAt` | string | Purchase timestamp \(ISO 8601\) | +| `updatedAt` | string | Last update timestamp \(ISO 8601\) | + +### `brex_upload_receipt` + +Upload a receipt file and attach it to a specific Brex card expense + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `expenseId` | string | Yes | ID of the card expense to attach the receipt to | +| `file` | file | Yes | Receipt file to upload \(max 50 MB\) | +| `receiptName` | string | No | Receipt file name including extension \(defaults to the uploaded file name\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `receiptId` | string | Unique identifier of the receipt upload | +| `receiptName` | string | Name the receipt was uploaded with | +| `expenseId` | string | ID of the expense the receipt was attached to | + +### `brex_match_receipt` + +Upload a receipt file and let Brex automatically match it with existing expenses + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `file` | file | Yes | Receipt file to upload \(max 50 MB\) | +| `receiptName` | string | No | Receipt file name including extension \(defaults to the uploaded file name\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `receiptId` | string | Unique identifier of the receipt match request | +| `receiptName` | string | Name the receipt was uploaded with | +| `expenseId` | string | Always null for receipt match \(Brex matches the receipt asynchronously\) | + +### `brex_list_card_transactions` + +List settled card transactions for all Brex card accounts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `userIds` | string | No | Comma-separated user IDs to filter transactions by cardholder | +| `postedAtStart` | string | No | Only include transactions posted at or after this ISO 8601 timestamp | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of transactions to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Settled card transactions | +| ↳ `id` | string | Unique transaction ID | +| ↳ `card_id` | string | ID of the card used | +| ↳ `description` | string | Transaction description | +| ↳ `amount` | json | Transaction amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `initiated_at_date` | string | Date the transaction was initiated | +| ↳ `posted_at_date` | string | Date the transaction was posted | +| ↳ `type` | string | Transaction type \(PURCHASE, REFUND, CHARGEBACK, REWARDS_CREDIT, COLLECTION, BNPL_FEE\) | +| ↳ `merchant` | json | Merchant details | +| ↳ `raw_descriptor` | string | Raw merchant descriptor | +| ↳ `mcc` | string | Merchant category code | +| ↳ `country` | string | Merchant country | +| ↳ `expense_id` | string | Associated expense ID | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_cash_transactions` + +List transactions for a Brex cash account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `accountId` | string | Yes | ID of the cash account to list transactions for | +| `postedAtStart` | string | No | Only include transactions posted at or after this ISO 8601 timestamp | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of transactions to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Cash account transactions | +| ↳ `id` | string | Unique transaction ID | +| ↳ `description` | string | Transaction description | +| ↳ `amount` | json | Transaction amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `initiated_at_date` | string | Date the transaction was initiated | +| ↳ `posted_at_date` | string | Date the transaction was posted | +| ↳ `type` | string | Transaction type | +| ↳ `transfer_id` | string | Associated transfer ID | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_card_accounts` + +List all Brex card accounts with balances and limits + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `accounts` | array | Card accounts | +| ↳ `id` | string | Unique account ID | +| ↳ `status` | string | Account status | +| ↳ `current_balance` | json | Current balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `available_balance` | json | Available balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `account_limit` | json | Account limit | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `current_statement_period` | json | Current statement period \(start_date, end_date\) | + +### `brex_list_cash_accounts` + +List all Brex cash accounts with balances and account details + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of accounts to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Cash accounts | +| ↳ `id` | string | Unique account ID | +| ↳ `name` | string | Account name | +| ↳ `status` | string | Account status | +| ↳ `current_balance` | json | Current balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `available_balance` | json | Available balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `account_number` | string | Bank account number | +| ↳ `routing_number` | string | Bank routing number | +| ↳ `primary` | boolean | Whether this is the primary cash account | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_cash_account` + +Get a Brex cash account by ID, or the primary cash account when no ID is provided + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `accountId` | string | No | ID of the cash account \(defaults to the primary cash account\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique account ID | +| `name` | string | Account name | +| `status` | string | Account status | +| `currentBalance` | json | Current balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `availableBalance` | json | Available balance | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `accountNumber` | string | Bank account number | +| `routingNumber` | string | Bank routing number | +| `primary` | boolean | Whether this is the primary cash account | + +### `brex_list_card_statements` + +List finalized statements for the primary Brex card account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of statements to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Finalized card account statements | +| ↳ `id` | string | Unique statement ID | +| ↳ `start_balance` | json | Balance at the start of the period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `end_balance` | json | Balance at the end of the period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `period` | json | Statement period \(start_date, end_date\) | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_cash_statements` + +List finalized statements for a Brex cash account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `accountId` | string | Yes | ID of the cash account to list statements for | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of statements to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Finalized cash account statements | +| ↳ `id` | string | Unique statement ID | +| ↳ `start_balance` | json | Balance at the start of the period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `end_balance` | json | Balance at the end of the period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `period` | json | Statement period \(start_date, end_date\) | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_users` + +List users in the Brex account, optionally filtered by email + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `email` | string | No | Filter users by exact email address | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of users to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Users in the Brex account | +| ↳ `id` | string | Unique user ID | +| ↳ `first_name` | string | First name | +| ↳ `last_name` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `status` | string | User status \(INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED\) | +| ↳ `manager_id` | string | ID of the manager | +| ↳ `department_id` | string | Department ID | +| ↳ `location_id` | string | Location ID | +| ↳ `title_id` | string | Title ID | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_user` + +Get a Brex user by their ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `userId` | string | Yes | ID of the user to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique user ID | +| `firstName` | string | First name | +| `lastName` | string | Last name | +| `email` | string | Email address | +| `status` | string | User status \(INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED\) | +| `managerId` | string | ID of the manager | +| `departmentId` | string | Department ID | +| `locationId` | string | Location ID | +| `titleId` | string | Title ID | + +### `brex_get_current_user` + +Get the Brex user associated with the API token + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique user ID | +| `firstName` | string | First name | +| `lastName` | string | Last name | +| `email` | string | Email address | +| `status` | string | User status \(INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED\) | +| `managerId` | string | ID of the manager | +| `departmentId` | string | Department ID | +| `locationId` | string | Location ID | +| `titleId` | string | Title ID | + +### `brex_list_departments` + +List departments in the Brex account, optionally filtered by name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `name` | string | No | Filter departments by name | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of departments to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Departments in the Brex account | +| ↳ `id` | string | Unique department ID | +| ↳ `name` | string | Department name | +| ↳ `description` | string | Department description | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_locations` + +List locations in the Brex account, optionally filtered by name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `name` | string | No | Filter locations by name | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of locations to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Locations in the Brex account | +| ↳ `id` | string | Unique location ID | +| ↳ `name` | string | Location name | +| ↳ `description` | string | Location description | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_titles` + +List job titles in the Brex account, optionally filtered by name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `name` | string | No | Filter titles by name | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of titles to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Job titles in the Brex account | +| ↳ `id` | string | Unique title ID | +| ↳ `name` | string | Title name | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_list_cards` + +List cards in the Brex account, optionally filtered by card owner + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `userId` | string | No | Filter cards by the ID of the card owner | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of cards to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Cards in the Brex account | +| ↳ `id` | string | Unique card ID | +| ↳ `owner` | json | Card owner \(type, user_id\) | +| ↳ `status` | string | Card status | +| ↳ `last_four` | string | Last four digits of the card number | +| ↳ `card_name` | string | Card name | +| ↳ `card_type` | string | Card type \(VIRTUAL or PHYSICAL\) | +| ↳ `limit_type` | string | Limit type \(CARD or USER\) | +| ↳ `spend_controls` | json | Spend controls on the card | +| ↳ `billing_address` | json | Billing address of the card | +| ↳ `expiration_date` | json | Card expiration date \(month, year\) | +| ↳ `budget_id` | string | Associated budget ID | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_company` + +Get the Brex company associated with the API token + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique company ID | +| `legalName` | string | Legal name of the company | +| `mailingAddress` | json | Company mailing address \(line1, line2, city, state, country, postal_code\) | +| `accountType` | string | Brex account type \(BREX_CLASSIC or BREX_EMPOWER\) | + +### `brex_list_budgets` + +List budgets in the Brex account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of budgets to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Budgets in the Brex account | +| ↳ `budget_id` | string | Unique budget ID | +| ↳ `account_id` | string | Account ID the budget belongs to | +| ↳ `name` | string | Budget name | +| ↳ `description` | string | Budget description | +| ↳ `parent_budget_id` | string | Parent budget ID | +| ↳ `owner_user_ids` | array | User IDs of the budget owners | +| ↳ `period_recurrence_type` | string | Budget period recurrence \(WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME\) | +| ↳ `start_date` | string | Budget start date | +| ↳ `end_date` | string | Budget end date | +| ↳ `amount` | json | Budget amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `spend_budget_status` | string | Budget status | +| ↳ `limit_type` | string | Budget limit type | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_budget` + +Get a Brex budget by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `budgetId` | string | Yes | ID of the budget to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `budgetId` | string | Unique budget ID | +| `accountId` | string | Account ID the budget belongs to | +| `name` | string | Budget name | +| `description` | string | Budget description | +| `parentBudgetId` | string | Parent budget ID | +| `ownerUserIds` | array | User IDs of the budget owners | +| `periodRecurrenceType` | string | Budget period recurrence \(WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME\) | +| `startDate` | string | Budget start date | +| `endDate` | string | Budget end date | +| `amount` | json | Budget amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `spendBudgetStatus` | string | Budget status \(ACTIVE, ARCHIVED, DELETED, EXPIRED\) | +| `limitType` | string | Budget limit type \(HARD or SOFT\) | + +### `brex_list_spend_limits` + +List spend limits in the Brex account, optionally filtered by member user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `memberUserIds` | string | No | Comma-separated user IDs to filter spend limits by member | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of spend limits to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Spend limits in the Brex account | +| ↳ `id` | string | Unique spend limit ID | +| ↳ `account_id` | string | Account ID the spend limit belongs to | +| ↳ `name` | string | Spend limit name | +| ↳ `description` | string | Spend limit description | +| ↳ `parent_budget_id` | string | Parent budget ID | +| ↳ `status` | string | Spend limit status | +| ↳ `period_recurrence_type` | string | Period recurrence \(PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME\) | +| ↳ `spend_type` | string | Spend type of the limit | +| ↳ `owner_user_ids` | array | User IDs of the spend limit owners | +| ↳ `member_user_ids` | array | User IDs of the spend limit members | +| ↳ `current_period_balance` | json | Spend and rollover amounts for the current period | +| ↳ `start_date` | string | Start date of the current period | +| ↳ `end_date` | string | End date of the current period | +| ↳ `start_time` | string | Start time of the current period \(ISO 8601\) | +| ↳ `end_time` | string | End time of the current period \(ISO 8601\) | +| ↳ `amount_spent` | json | Amount spent in the current period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `rollover_amount` | json | Amount rolled over from previous periods | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_spend_limit` + +Get a Brex spend limit by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `spendLimitId` | string | Yes | ID of the spend limit to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique spend limit ID | +| `accountId` | string | Account ID the spend limit belongs to | +| `name` | string | Spend limit name | +| `description` | string | Spend limit description | +| `parentBudgetId` | string | Parent budget ID | +| `status` | string | Spend limit status \(ACTIVE, EXPIRED, ARCHIVED, DELETED\) | +| `periodRecurrenceType` | string | Period recurrence \(PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME\) | +| `spendType` | string | Spend type of the limit | +| `startDate` | string | Spend limit start date | +| `endDate` | string | Spend limit end date | +| `ownerUserIds` | array | User IDs of the spend limit owners | +| `memberUserIds` | array | User IDs of the spend limit members | +| `currentPeriodBalance` | json | Spend and rollover amounts for the current period | +| ↳ `start_date` | string | Start date of the current period | +| ↳ `end_date` | string | End date of the current period | +| ↳ `start_time` | string | Start time of the current period \(ISO 8601\) | +| ↳ `end_time` | string | End time of the current period \(ISO 8601\) | +| ↳ `amount_spent` | json | Amount spent in the current period | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `rollover_amount` | json | Amount rolled over from previous periods | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `authorizationSettings` | json | Authorization settings \(base limit, authorization type, rollover refresh\) | + +### `brex_list_vendors` + +List vendors in the Brex account, optionally filtered by name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `name` | string | No | Filter vendors by name | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of vendors to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Vendors in the Brex account | +| ↳ `id` | string | Unique vendor ID | +| ↳ `company_name` | string | Vendor company name | +| ↳ `email` | string | Vendor email address | +| ↳ `phone` | string | Vendor phone number | +| ↳ `payment_accounts` | array | Payment accounts associated with the vendor | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_vendor` + +Get a Brex vendor by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `vendorId` | string | Yes | ID of the vendor to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique vendor ID | +| `companyName` | string | Vendor company name | +| `email` | string | Vendor email address | +| `phone` | string | Vendor phone number | +| `paymentAccounts` | array | Payment accounts associated with the vendor | + +### `brex_list_transfers` + +List money transfers in the Brex account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `cursor` | string | No | Pagination cursor from a previous response | +| `limit` | string | No | Number of transfers to return \(default 100, max 1000\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Transfers in the Brex account | +| ↳ `id` | string | Unique transfer ID | +| ↳ `counterparty` | json | Transfer counterparty details | +| ↳ `description` | string | Transfer description | +| ↳ `payment_type` | string | Payment type \(ACH, DOMESTIC_WIRE, CHEQUE, INTERNATIONAL_WIRE, BOOK_TRANSFER, STABLECOIN\) | +| ↳ `amount` | json | Transfer amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| ↳ `process_date` | string | Date the transfer processes | +| ↳ `originating_account` | json | Account the transfer originates from | +| ↳ `status` | string | Transfer status \(PROCESSING, SCHEDULED, PENDING_APPROVAL, FAILED, PROCESSED\) | +| ↳ `cancellation_reason` | string | Reason the transfer was canceled | +| ↳ `estimated_delivery_date` | string | Estimated delivery date | +| ↳ `creator_user_id` | string | ID of the user who created the transfer | +| ↳ `created_at` | string | Creation timestamp | +| ↳ `display_name` | string | Transfer display name | +| ↳ `external_memo` | string | External memo | +| `nextCursor` | string | Cursor for fetching the next page of results | + +### `brex_get_transfer` + +Get a Brex money transfer by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Brex user token \(generated from Developer Settings in the Brex dashboard\) | +| `transferId` | string | Yes | ID of the transfer to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique transfer ID | +| `counterparty` | json | Transfer counterparty details | +| `description` | string | Transfer description | +| `paymentType` | string | Payment type \(ACH, DOMESTIC_WIRE, CHEQUE, INTERNATIONAL_WIRE, BOOK_TRANSFER, STABLECOIN\) | +| `amount` | json | Transfer amount | +| ↳ `amount` | number | Amount in the smallest unit of the currency \(e.g., cents for USD\) | +| ↳ `currency` | string | ISO 4217 currency code \(e.g., USD\) | +| `processDate` | string | Date the transfer processes | +| `originatingAccount` | json | Account the transfer originates from | +| `status` | string | Transfer status \(PROCESSING, SCHEDULED, PENDING_APPROVAL, FAILED, PROCESSED\) | +| `cancellationReason` | string | Reason the transfer was canceled | +| `estimatedDeliveryDate` | string | Estimated delivery date | +| `creatorUserId` | string | ID of the user who created the transfer | +| `createdAt` | string | Creation timestamp | +| `displayName` | string | Transfer display name | +| `externalMemo` | string | External memo | + + diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index d96c79dba7..14d9f23ab7 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -21,6 +21,7 @@ "azure_devops", "box", "brandfetch", + "brex", "brightdata", "browser_use", "calcom", diff --git a/apps/sim/app/api/tools/brex/upload-receipt/route.test.ts b/apps/sim/app/api/tools/brex/upload-receipt/route.test.ts new file mode 100644 index 0000000000..4453ddc1df --- /dev/null +++ b/apps/sim/app/api/tools/brex/upload-receipt/route.test.ts @@ -0,0 +1,245 @@ +/** + * @vitest-environment node + */ +import { + createMockRequest, + hybridAuthMockFns, + inputValidationMock, + inputValidationMockFns, +} from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockProcessFilesToUserFiles, mockDownloadFileFromStorage, mockAssertToolFileAccess } = + vi.hoisted(() => ({ + mockProcessFilesToUserFiles: vi.fn(), + mockDownloadFileFromStorage: vi.fn(), + mockAssertToolFileAccess: vi.fn(), + })) + +vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock) +vi.mock('@/lib/uploads/utils/file-utils', () => ({ + processFilesToUserFiles: mockProcessFilesToUserFiles, +})) +vi.mock('@/lib/uploads/utils/file-utils.server', () => ({ + downloadFileFromStorage: mockDownloadFileFromStorage, +})) +vi.mock('@/app/api/files/authorization', () => ({ + assertToolFileAccess: mockAssertToolFileAccess, +})) + +import { POST } from '@/app/api/tools/brex/upload-receipt/route' + +const mockFetch = vi.fn() + +const PINNED_IP = '52.216.0.1' + +const baseBody = { + apiKey: 'bxt_test_token', + expenseId: 'expense_123', + file: { key: 'uploads/receipt.pdf', name: 'receipt.pdf', size: 5, type: 'application/pdf' }, +} + +function jsonResponse(body: unknown, status = 200) { + return { + ok: status >= 200 && status < 300, + status, + text: async () => JSON.stringify(body), + json: async () => body, + } +} + +beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-1', + authType: 'internal_jwt', + }) + inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValue({ + isValid: true, + resolvedIP: PINNED_IP, + }) + inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValue(jsonResponse({})) + mockProcessFilesToUserFiles.mockReturnValue([ + { key: 'uploads/receipt.pdf', name: 'receipt.pdf', size: 5, type: 'application/pdf' }, + ]) + mockAssertToolFileAccess.mockResolvedValue(null) + mockDownloadFileFromStorage.mockResolvedValue(Buffer.from('receipt-bytes')) +}) + +describe('POST /api/tools/brex/upload-receipt', () => { + it('rejects unauthenticated requests', async () => { + hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValueOnce({ + success: false, + error: 'unauthorized', + }) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(401) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('creates a receipt upload for an expense and PUTs the file to the pre-signed URL', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_1', uri: 'https://s3.example.com/presigned' }) + ) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(200) + const data = await response.json() + expect(data).toEqual({ + success: true, + output: { receiptId: 'receipt_1', receiptName: 'receipt.pdf', expenseId: 'expense_123' }, + }) + + expect(mockFetch).toHaveBeenCalledTimes(1) + const [createUrl, createInit] = mockFetch.mock.calls[0] + expect(createUrl).toBe('https://api.brex.com/v1/expenses/card/expense_123/receipt_upload') + expect(createInit.method).toBe('POST') + expect(createInit.headers.Authorization).toBe('Bearer bxt_test_token') + expect(JSON.parse(createInit.body)).toEqual({ receipt_name: 'receipt.pdf' }) + + expect(inputValidationMockFns.mockValidateUrlWithDNS).toHaveBeenCalledWith( + 'https://s3.example.com/presigned', + 'uri' + ) + const [uploadUrl, pinnedIP, uploadInit] = + inputValidationMockFns.mockSecureFetchWithPinnedIP.mock.calls[0] + expect(uploadUrl).toBe('https://s3.example.com/presigned') + expect(pinnedIP).toBe(PINNED_IP) + expect(uploadInit.method).toBe('PUT') + }) + + it('rejects a whitespace-only expense ID instead of falling back to receipt match', async () => { + const response = await POST(createMockRequest('POST', { ...baseBody, expenseId: ' ' })) + expect(response.status).toBe(400) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('trims a padded expense ID before building the upload URL', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_5', uri: 'https://s3.example.com/presigned' }) + ) + + const response = await POST( + createMockRequest('POST', { ...baseBody, expenseId: ' expense_123 ' }) + ) + expect(response.status).toBe(200) + const [createUrl] = mockFetch.mock.calls[0] + expect(createUrl).toBe('https://api.brex.com/v1/expenses/card/expense_123/receipt_upload') + const data = await response.json() + expect(data.output.expenseId).toBe('expense_123') + }) + + it('rejects a whitespace-only receipt name', async () => { + const response = await POST(createMockRequest('POST', { ...baseBody, receiptName: ' ' })) + expect(response.status).toBe(400) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('rejects an API key containing header-breaking characters', async () => { + const response = await POST( + createMockRequest('POST', { ...baseBody, apiKey: 'bxt_test\r\nX-Injected: 1' }) + ) + expect(response.status).toBe(400) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('uses receipt match when no expense ID is provided', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_2', uri: 'https://s3.example.com/presigned' }) + ) + + const response = await POST( + createMockRequest('POST', { apiKey: 'bxt_test_token', file: baseBody.file }) + ) + expect(response.status).toBe(200) + const data = await response.json() + expect(data.output).toEqual({ + receiptId: 'receipt_2', + receiptName: 'receipt.pdf', + expenseId: null, + }) + + const [createUrl] = mockFetch.mock.calls[0] + expect(createUrl).toBe('https://api.brex.com/v1/expenses/card/receipt_match') + }) + + it('honors a receipt name override', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_3', uri: 'https://s3.example.com/presigned' }) + ) + + const response = await POST( + createMockRequest('POST', { ...baseBody, receiptName: 'march-dinner.pdf' }) + ) + expect(response.status).toBe(200) + const [, createInit] = mockFetch.mock.calls[0] + expect(JSON.parse(createInit.body)).toEqual({ receipt_name: 'march-dinner.pdf' }) + }) + + it('propagates Brex API errors', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse({ message: 'Expense not found' }, 404)) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(404) + const data = await response.json() + expect(data.success).toBe(false) + expect(data.error).toContain('Expense not found') + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).not.toHaveBeenCalled() + }) + + it('rejects files over the 50 MB limit', async () => { + mockDownloadFileFromStorage.mockResolvedValueOnce(Buffer.alloc(50 * 1024 * 1024 + 1)) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(400) + const data = await response.json() + expect(data.error).toContain('50 MB') + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('blocks pre-signed URLs that fail SSRF validation', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_6', uri: 'https://169.254.169.254/latest/meta-data' }) + ) + inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValueOnce({ + isValid: false, + error: 'uri resolves to a blocked IP address', + }) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(502) + const data = await response.json() + expect(data.error).toContain('invalid upload URL') + expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).not.toHaveBeenCalled() + }) + + it('fails when the pre-signed upload fails', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ id: 'receipt_4', uri: 'https://s3.example.com/presigned' }) + ) + inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValueOnce(jsonResponse({}, 403)) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(502) + const data = await response.json() + expect(data.success).toBe(false) + }) + + it('denies access to files the caller cannot read', async () => { + const deniedResponse = new Response( + JSON.stringify({ success: false, error: 'File not found' }), + { + status: 404, + } + ) + mockAssertToolFileAccess.mockResolvedValueOnce(deniedResponse) + + const response = await POST(createMockRequest('POST', baseBody)) + expect(response.status).toBe(404) + expect(mockFetch).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/tools/brex/upload-receipt/route.ts b/apps/sim/app/api/tools/brex/upload-receipt/route.ts new file mode 100644 index 0000000000..792e9ab6d5 --- /dev/null +++ b/apps/sim/app/api/tools/brex/upload-receipt/route.ts @@ -0,0 +1,147 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { brexUploadReceiptContract } from '@/lib/api/contracts/tools/brex' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { + secureFetchWithPinnedIP, + validateUrlWithDNS, +} from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { assertToolFileAccess } from '@/app/api/files/authorization' +import { BREX_API_BASE, buildBrexHeaders } from '@/tools/brex/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('BrexUploadReceiptAPI') + +const MAX_RECEIPT_SIZE_BYTES = 50 * 1024 * 1024 + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success || !authResult.userId) { + logger.warn(`[${requestId}] Unauthorized Brex receipt upload attempt: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + const parsed = await parseRequest(brexUploadReceiptContract, request, {}) + if (!parsed.success) return parsed.response + const { apiKey, expenseId, file, receiptName } = parsed.data.body + + const userFiles = processFilesToUserFiles([file as RawFileInput], requestId, logger) + if (userFiles.length === 0) { + return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 }) + } + + const userFile = userFiles[0] + const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger) + if (denied) return denied + + const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + if (fileBuffer.length > MAX_RECEIPT_SIZE_BYTES) { + return NextResponse.json( + { success: false, error: 'Receipt file exceeds the 50 MB limit' }, + { status: 400 } + ) + } + + const effectiveReceiptName = receiptName || userFile.name + const endpoint = expenseId + ? `${BREX_API_BASE}/v1/expenses/card/${encodeURIComponent(expenseId)}/receipt_upload` + : `${BREX_API_BASE}/v1/expenses/card/receipt_match` + + logger.info( + `[${requestId}] Creating Brex ${expenseId ? 'receipt upload' : 'receipt match'}: ${effectiveReceiptName} (${fileBuffer.length} bytes)` + ) + + const createResponse = await fetch(endpoint, { + method: 'POST', + headers: buildBrexHeaders(apiKey), + body: JSON.stringify({ receipt_name: effectiveReceiptName }), + }) + + if (!createResponse.ok) { + const errorText = await createResponse.text() + logger.error(`[${requestId}] Brex API error:`, { + status: createResponse.status, + error: errorText, + }) + let message = errorText + try { + message = JSON.parse(errorText).message ?? errorText + } catch { + message = errorText + } + return NextResponse.json( + { success: false, error: `Brex API error (${createResponse.status}): ${message}` }, + { status: createResponse.status } + ) + } + + const createData = await createResponse.json() + if (!createData.uri || !createData.id) { + return NextResponse.json( + { success: false, error: 'Brex did not return an upload URL' }, + { status: 502 } + ) + } + + const uriValidation = await validateUrlWithDNS(createData.uri, 'uri') + if (!uriValidation.isValid) { + logger.error(`[${requestId}] Pre-signed upload URL failed SSRF validation:`, { + error: uriValidation.error, + }) + return NextResponse.json( + { success: false, error: 'Brex returned an invalid upload URL' }, + { status: 502 } + ) + } + + const uploadResponse = await secureFetchWithPinnedIP( + createData.uri, + uriValidation.resolvedIP!, + { + method: 'PUT', + body: new Uint8Array(fileBuffer), + } + ) + + if (!uploadResponse.ok) { + logger.error(`[${requestId}] Receipt upload to pre-signed URL failed:`, { + status: uploadResponse.status, + }) + return NextResponse.json( + { success: false, error: `Failed to upload receipt file (${uploadResponse.status})` }, + { status: 502 } + ) + } + + logger.info(`[${requestId}] Receipt uploaded successfully (ID: ${createData.id})`) + + return NextResponse.json({ + success: true, + output: { + receiptId: createData.id, + receiptName: effectiveReceiptName, + expenseId: expenseId ?? null, + }, + }) + } catch (error) { + logger.error(`[${requestId}] Unexpected error:`, error) + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Unknown error') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/blocks/blocks/brex.ts b/apps/sim/blocks/blocks/brex.ts new file mode 100644 index 0000000000..08e4648c39 --- /dev/null +++ b/apps/sim/blocks/blocks/brex.ts @@ -0,0 +1,692 @@ +import { BrexIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' +import type { BrexResponse } from '@/tools/brex/types' + +const PAGINATED_OPERATIONS = new Set([ + 'list_expenses', + 'list_card_transactions', + 'list_cash_transactions', + 'list_cash_accounts', + 'list_card_statements', + 'list_cash_statements', + 'list_users', + 'list_departments', + 'list_locations', + 'list_titles', + 'list_cards', + 'list_budgets', + 'list_spend_limits', + 'list_vendors', + 'list_transfers', +]) + +export const BrexBlock: BlockConfig = { + type: 'brex', + name: 'Brex', + description: 'Manage expenses, receipts, transactions, and team data in Brex', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrates Brex into the workflow. List and update expenses, upload and match receipts, view card and cash transactions, accounts, budgets, spend limits, vendors, transfers, and team data.', + docsLink: 'https://docs.sim.ai/integrations/brex', + category: 'tools', + integrationType: IntegrationType.Commerce, + bgColor: '#171717', + icon: BrexIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + // Expenses + { label: 'List Expenses', id: 'list_expenses' }, + { label: 'Get Expense', id: 'get_expense' }, + { label: 'Update Expense Memo', id: 'update_expense' }, + { label: 'Upload Receipt', id: 'upload_receipt' }, + { label: 'Match Receipt', id: 'match_receipt' }, + // Transactions & accounts + { label: 'List Card Transactions', id: 'list_card_transactions' }, + { label: 'List Cash Transactions', id: 'list_cash_transactions' }, + { label: 'List Card Accounts', id: 'list_card_accounts' }, + { label: 'List Cash Accounts', id: 'list_cash_accounts' }, + { label: 'Get Cash Account', id: 'get_cash_account' }, + { label: 'List Card Statements', id: 'list_card_statements' }, + { label: 'List Cash Statements', id: 'list_cash_statements' }, + // Team + { label: 'List Users', id: 'list_users' }, + { label: 'Get User', id: 'get_user' }, + { label: 'Get Current User', id: 'get_current_user' }, + { label: 'List Departments', id: 'list_departments' }, + { label: 'List Locations', id: 'list_locations' }, + { label: 'List Titles', id: 'list_titles' }, + { label: 'List Cards', id: 'list_cards' }, + { label: 'Get Company', id: 'get_company' }, + // Budgets + { label: 'List Budgets', id: 'list_budgets' }, + { label: 'Get Budget', id: 'get_budget' }, + { label: 'List Spend Limits', id: 'list_spend_limits' }, + { label: 'Get Spend Limit', id: 'get_spend_limit' }, + // Payments + { label: 'List Vendors', id: 'list_vendors' }, + { label: 'Get Vendor', id: 'get_vendor' }, + { label: 'List Transfers', id: 'list_transfers' }, + { label: 'Get Transfer', id: 'get_transfer' }, + ], + value: () => 'list_expenses', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + password: true, + placeholder: 'Enter your Brex user token', + required: true, + }, + { + id: 'expenseId', + title: 'Expense ID', + type: 'short-input', + placeholder: 'ID of the expense', + condition: { + field: 'operation', + value: ['get_expense', 'update_expense', 'upload_receipt'], + }, + required: { + field: 'operation', + value: ['get_expense', 'update_expense', 'upload_receipt'], + }, + }, + { + id: 'memo', + title: 'Memo', + type: 'long-input', + placeholder: 'New memo for the expense', + condition: { field: 'operation', value: 'update_expense' }, + required: { field: 'operation', value: 'update_expense' }, + }, + { + id: 'uploadReceiptFile', + title: 'Receipt File', + type: 'file-upload', + canonicalParamId: 'file', + placeholder: 'Upload receipt file', + mode: 'basic', + multiple: false, + condition: { field: 'operation', value: ['upload_receipt', 'match_receipt'] }, + required: { field: 'operation', value: ['upload_receipt', 'match_receipt'] }, + }, + { + id: 'receiptFileReference', + title: 'Receipt File', + type: 'short-input', + canonicalParamId: 'file', + placeholder: 'Reference a file from a previous block', + mode: 'advanced', + condition: { field: 'operation', value: ['upload_receipt', 'match_receipt'] }, + required: { field: 'operation', value: ['upload_receipt', 'match_receipt'] }, + }, + { + id: 'receiptName', + title: 'Receipt Name', + type: 'short-input', + placeholder: 'Receipt file name with extension (defaults to the uploaded file name)', + mode: 'advanced', + condition: { field: 'operation', value: ['upload_receipt', 'match_receipt'] }, + }, + { + id: 'accountId', + title: 'Cash Account ID', + type: 'short-input', + 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'], + }, + required: { + field: 'operation', + value: ['list_cash_transactions', 'list_cash_statements'], + }, + }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'ID of the user (optional filter for List Cards)', + condition: { field: 'operation', value: ['get_user', 'list_cards'] }, + required: { field: 'operation', value: 'get_user' }, + }, + { + id: 'budgetId', + title: 'Budget ID', + type: 'short-input', + placeholder: 'ID of the budget', + condition: { field: 'operation', value: 'get_budget' }, + required: { field: 'operation', value: 'get_budget' }, + }, + { + id: 'spendLimitId', + title: 'Spend Limit ID', + type: 'short-input', + placeholder: 'ID of the spend limit', + condition: { field: 'operation', value: 'get_spend_limit' }, + required: { field: 'operation', value: 'get_spend_limit' }, + }, + { + id: 'vendorId', + title: 'Vendor ID', + type: 'short-input', + placeholder: 'ID of the vendor', + condition: { field: 'operation', value: 'get_vendor' }, + required: { field: 'operation', value: 'get_vendor' }, + }, + { + id: 'transferId', + title: 'Transfer ID', + type: 'short-input', + placeholder: 'ID of the transfer', + condition: { field: 'operation', value: 'get_transfer' }, + required: { field: 'operation', value: 'get_transfer' }, + }, + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'Filter users by exact email address', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'name', + title: 'Name Filter', + type: 'short-input', + placeholder: 'Filter results by name', + mode: 'advanced', + condition: { + field: 'operation', + value: ['list_departments', 'list_locations', 'list_titles', 'list_vendors'], + }, + }, + { + id: 'userIds', + title: 'User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs to filter by', + mode: 'advanced', + condition: { field: 'operation', value: ['list_expenses', 'list_card_transactions'] }, + }, + { + id: 'statuses', + title: 'Expense Statuses', + type: 'short-input', + placeholder: 'e.g., APPROVED, SETTLED (comma-separated)', + mode: 'advanced', + condition: { field: 'operation', value: 'list_expenses' }, + }, + { + id: 'paymentStatuses', + title: 'Payment Statuses', + type: 'short-input', + placeholder: 'e.g., CLEARED, REFUNDED (comma-separated)', + mode: 'advanced', + condition: { field: 'operation', value: 'list_expenses' }, + }, + { + id: 'purchasedAtStart', + title: 'Purchased After', + type: 'short-input', + placeholder: 'ISO 8601 timestamp (e.g., 2026-01-01T00:00:00Z)', + mode: 'advanced', + condition: { field: 'operation', value: 'list_expenses' }, + wandConfig: { + enabled: true, + generationType: 'timestamp', + prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.', + placeholder: 'Describe the start date (e.g., "beginning of last month")...', + }, + }, + { + id: 'purchasedAtEnd', + title: 'Purchased Before', + type: 'short-input', + placeholder: 'ISO 8601 timestamp (e.g., 2026-02-01T00:00:00Z)', + mode: 'advanced', + condition: { field: 'operation', value: 'list_expenses' }, + wandConfig: { + enabled: true, + generationType: 'timestamp', + prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.', + placeholder: 'Describe the end date (e.g., "end of last month")...', + }, + }, + { + id: 'postedAtStart', + title: 'Posted After', + type: 'short-input', + placeholder: 'ISO 8601 timestamp (e.g., 2026-01-01T00:00:00Z)', + mode: 'advanced', + condition: { + field: 'operation', + value: ['list_card_transactions', 'list_cash_transactions'], + }, + wandConfig: { + enabled: true, + generationType: 'timestamp', + prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.', + placeholder: 'Describe the start date (e.g., "last Monday")...', + }, + }, + { + id: 'memberUserIds', + title: 'Member User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs to filter spend limits by member', + mode: 'advanced', + condition: { field: 'operation', value: 'list_spend_limits' }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Pagination cursor from a previous response', + mode: 'advanced', + condition: { + field: 'operation', + value: [ + 'list_expenses', + 'list_card_transactions', + 'list_cash_transactions', + 'list_cash_accounts', + 'list_card_statements', + 'list_cash_statements', + 'list_users', + 'list_departments', + 'list_locations', + 'list_titles', + 'list_cards', + 'list_budgets', + 'list_spend_limits', + 'list_vendors', + 'list_transfers', + ], + }, + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Number of results to return (default 100; List Expenses caps at 100)', + mode: 'advanced', + condition: { + field: 'operation', + value: [ + 'list_expenses', + 'list_card_transactions', + 'list_cash_transactions', + 'list_cash_accounts', + 'list_card_statements', + 'list_cash_statements', + 'list_users', + 'list_departments', + 'list_locations', + 'list_titles', + 'list_cards', + 'list_budgets', + 'list_spend_limits', + 'list_vendors', + 'list_transfers', + ], + }, + }, + ], + tools: { + access: [ + 'brex_list_expenses', + 'brex_get_expense', + 'brex_update_expense', + 'brex_upload_receipt', + 'brex_match_receipt', + 'brex_list_card_transactions', + 'brex_list_cash_transactions', + 'brex_list_card_accounts', + 'brex_list_cash_accounts', + 'brex_get_cash_account', + 'brex_list_card_statements', + 'brex_list_cash_statements', + 'brex_list_users', + 'brex_get_user', + 'brex_get_current_user', + 'brex_list_departments', + 'brex_list_locations', + 'brex_list_titles', + 'brex_list_cards', + 'brex_get_company', + 'brex_list_budgets', + 'brex_get_budget', + 'brex_list_spend_limits', + 'brex_get_spend_limit', + 'brex_list_vendors', + 'brex_get_vendor', + 'brex_list_transfers', + 'brex_get_transfer', + ], + config: { + tool: (params) => `brex_${params.operation}`, + params: (params) => { + const { operation, apiKey } = params + const result: Record = { apiKey } + + switch (operation) { + case 'list_expenses': + if (params.userIds) result.userIds = params.userIds + if (params.statuses) result.statuses = params.statuses + if (params.paymentStatuses) result.paymentStatuses = params.paymentStatuses + if (params.purchasedAtStart) result.purchasedAtStart = params.purchasedAtStart + if (params.purchasedAtEnd) result.purchasedAtEnd = params.purchasedAtEnd + break + case 'get_expense': + result.expenseId = params.expenseId + break + case 'update_expense': + result.expenseId = params.expenseId + result.memo = params.memo + break + case 'upload_receipt': + case 'match_receipt': { + const file = normalizeFileInput(params.file, { single: true }) + if (file) result.file = file + if (operation === 'upload_receipt') result.expenseId = params.expenseId + if (params.receiptName) result.receiptName = params.receiptName + break + } + case 'list_card_transactions': + if (params.userIds) result.userIds = params.userIds + if (params.postedAtStart) result.postedAtStart = params.postedAtStart + break + case 'list_cash_transactions': + result.accountId = params.accountId + if (params.postedAtStart) result.postedAtStart = params.postedAtStart + break + case 'list_cash_statements': + result.accountId = params.accountId + break + case 'get_cash_account': + if (params.accountId) result.accountId = params.accountId + break + case 'list_users': + if (params.email) result.email = params.email + break + case 'get_user': + result.userId = params.userId + break + case 'list_cards': + if (params.userId) result.userId = params.userId + break + case 'list_departments': + case 'list_locations': + case 'list_titles': + case 'list_vendors': + if (params.name) result.name = params.name + break + case 'list_spend_limits': + if (params.memberUserIds) result.memberUserIds = params.memberUserIds + break + case 'get_budget': + result.budgetId = params.budgetId + break + case 'get_spend_limit': + result.spendLimitId = params.spendLimitId + break + case 'get_vendor': + result.vendorId = params.vendorId + break + case 'get_transfer': + result.transferId = params.transferId + break + default: + break + } + + if (PAGINATED_OPERATIONS.has(operation)) { + if (params.cursor) result.cursor = String(params.cursor) + if (params.limit) result.limit = String(params.limit) + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Brex user token' }, + expenseId: { type: 'string', description: 'Expense ID' }, + memo: { type: 'string', description: 'New memo for the expense' }, + file: { type: 'json', description: 'Receipt file to upload (canonical param)' }, + receiptName: { type: 'string', description: 'Receipt file name including extension' }, + accountId: { type: 'string', description: 'Cash account ID' }, + userId: { type: 'string', description: 'User ID' }, + budgetId: { type: 'string', description: 'Budget ID' }, + spendLimitId: { type: 'string', description: 'Spend limit ID' }, + vendorId: { type: 'string', description: 'Vendor ID' }, + transferId: { type: 'string', description: 'Transfer ID' }, + email: { type: 'string', description: 'Email filter for listing users' }, + name: { type: 'string', description: 'Name filter for departments, locations, or vendors' }, + userIds: { type: 'string', description: 'Comma-separated user IDs filter' }, + statuses: { type: 'string', description: 'Comma-separated expense statuses filter' }, + paymentStatuses: { type: 'string', description: 'Comma-separated payment statuses filter' }, + purchasedAtStart: { type: 'string', description: 'Purchased-after ISO 8601 timestamp filter' }, + purchasedAtEnd: { type: 'string', description: 'Purchased-before ISO 8601 timestamp filter' }, + postedAtStart: { type: 'string', description: 'Posted-after ISO 8601 timestamp filter' }, + memberUserIds: { + type: 'string', + description: 'Comma-separated member user IDs filter for spend limits', + }, + cursor: { type: 'string', description: 'Pagination cursor' }, + limit: { type: 'string', description: 'Number of results to return' }, + }, + outputs: { + items: { type: 'json', description: 'Items returned by list operations' }, + nextCursor: { type: 'string', description: 'Cursor for fetching the next page of results' }, + accounts: { type: 'json', description: 'Card accounts returned by List Card Accounts' }, + id: { type: 'string', description: 'ID of the fetched or updated resource' }, + memo: { type: 'string', description: 'Memo on the expense' }, + status: { type: 'string', description: 'Status of the expense or user' }, + paymentStatus: { type: 'string', description: 'Payment status of the expense' }, + expenseType: { type: 'string', description: 'Type of the expense' }, + category: { type: 'string', description: 'Merchant category of the expense' }, + merchantId: { type: 'string', description: 'Merchant ID' }, + merchant: { type: 'json', description: 'Merchant details' }, + budgetId: { type: 'string', description: 'Budget ID' }, + budget: { type: 'json', description: 'Budget details' }, + departmentId: { type: 'string', description: 'Department ID' }, + department: { type: 'json', description: 'Department details' }, + locationId: { type: 'string', description: 'Location ID' }, + location: { type: 'json', description: 'Location details' }, + userId: { type: 'string', description: 'User ID associated with the expense' }, + user: { type: 'json', description: 'User details' }, + originalAmount: { type: 'json', description: 'Original transaction amount' }, + billingAmount: { type: 'json', description: 'Amount billed to the account' }, + purchasedAmount: { type: 'json', description: 'Amount at the time of purchase' }, + usdEquivalentAmount: { type: 'json', description: 'USD equivalent amount' }, + purchasedAt: { type: 'string', description: 'Purchase timestamp' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + paymentPostedAt: { type: 'string', description: 'Timestamp the payment was posted' }, + receipts: { type: 'json', description: 'Receipts attached to the expense' }, + dashboardUrl: { type: 'string', description: 'Link to the expense in the Brex dashboard' }, + receiptId: { type: 'string', description: 'ID of the uploaded or matched receipt' }, + receiptName: { type: 'string', description: 'Name the receipt was uploaded with' }, + 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' }, + 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' }, + mailingAddress: { type: 'json', description: 'Mailing address of the company' }, + accountType: { type: 'string', description: 'Brex account type of the company' }, + name: { type: 'string', description: 'Name of the account, budget, or spend limit' }, + currentBalance: { type: 'json', description: 'Current balance of the cash account' }, + availableBalance: { type: 'json', description: 'Available balance of the cash account' }, + accountNumber: { type: 'string', description: 'Bank account number of the cash account' }, + routingNumber: { type: 'string', description: 'Bank routing number of the cash account' }, + primary: { type: 'boolean', description: 'Whether the cash account is primary' }, + accountId: { type: 'string', description: 'Account ID of the budget or spend limit' }, + description: { type: 'string', description: 'Description of the budget or spend limit' }, + parentBudgetId: { type: 'string', description: 'Parent budget ID' }, + ownerUserIds: { type: 'json', description: 'Owner user IDs of the budget or spend limit' }, + memberUserIds: { type: 'json', description: 'Member user IDs of the spend limit' }, + periodRecurrenceType: { type: 'string', description: 'Period recurrence type' }, + spendType: { type: 'string', description: 'Spend type of the spend limit' }, + startDate: { type: 'string', description: 'Start date of the budget or spend limit' }, + endDate: { type: 'string', description: 'End date of the budget or spend limit' }, + amount: { type: 'json', description: 'Amount of the budget' }, + spendBudgetStatus: { type: 'string', description: 'Status of the budget' }, + limitType: { type: 'string', description: 'Limit type of the budget' }, + currentPeriodBalance: { + type: 'json', + description: 'Current period balance of the spend limit', + }, + authorizationSettings: { + type: 'json', + description: 'Authorization settings of the spend limit', + }, + companyName: { type: 'string', description: 'Company name of the vendor' }, + phone: { type: 'string', description: 'Phone number of the vendor' }, + paymentAccounts: { type: 'json', description: 'Payment accounts of the vendor' }, + counterparty: { type: 'json', description: 'Counterparty of the transfer' }, + paymentType: { type: 'string', description: 'Payment type of the transfer' }, + processDate: { type: 'string', description: 'Process date of the transfer' }, + originatingAccount: { type: 'json', description: 'Originating account of the transfer' }, + cancellationReason: { type: 'string', description: 'Cancellation reason of the transfer' }, + estimatedDeliveryDate: { + type: 'string', + description: 'Estimated delivery date of the transfer', + }, + creatorUserId: { type: 'string', description: 'ID of the user who created the transfer' }, + createdAt: { type: 'string', description: 'Creation timestamp of the transfer' }, + displayName: { type: 'string', description: 'Display name of the transfer' }, + externalMemo: { type: 'string', description: 'External memo of the transfer' }, + }, +} + +export const BrexBlockMeta = { + tags: ['payments'], + templates: [ + { + icon: BrexIcon, + title: 'Brex receipt auto-attach', + prompt: + 'Build a workflow that takes an uploaded receipt file and sends it to Brex with the Match Receipt operation so Brex automatically pairs it with the right card expense.', + modules: ['workflows', 'files'], + category: 'operations', + tags: ['automation'], + }, + { + icon: BrexIcon, + title: 'Brex daily expense digest', + prompt: + 'Build a scheduled workflow that runs every weekday morning, lists Brex expenses settled in the last 24 hours, summarizes total spend by merchant category, and posts the digest to a Slack channel.', + modules: ['workflows', 'scheduled'], + category: 'operations', + tags: ['automation'], + alsoIntegrations: ['slack'], + }, + { + icon: BrexIcon, + title: 'Brex memo enforcer', + prompt: + 'Build a scheduled workflow that lists approved Brex expenses, finds ones missing a memo, has an agent draft a memo from the merchant and amount details, and updates each expense with the drafted memo.', + modules: ['agent', 'workflows', 'scheduled'], + category: 'operations', + tags: ['automation'], + }, + { + icon: BrexIcon, + title: 'Brex spend anomaly alert', + prompt: + 'Build a scheduled workflow that lists recent Brex card transactions, flags any transaction above a configurable threshold, and emails the finance team a report of flagged transactions with merchant details.', + modules: ['workflows', 'scheduled'], + category: 'operations', + tags: ['automation'], + alsoIntegrations: ['gmail'], + }, + { + icon: BrexIcon, + title: 'Brex cash balance monitor', + prompt: + 'Build a scheduled workflow that checks Brex cash account balances every morning and sends a Slack alert when the available balance of any account drops below a set threshold.', + modules: ['workflows', 'scheduled'], + category: 'operations', + tags: ['automation'], + alsoIntegrations: ['slack'], + }, + { + icon: BrexIcon, + title: 'Brex budget utilization report', + prompt: + 'Build a weekly workflow that lists Brex budgets and spend limits, computes utilization for each budget from its amount and current period balance, stores the results in a table, and emails a summary report.', + modules: ['workflows', 'scheduled', 'tables'], + category: 'operations', + tags: ['automation'], + alsoIntegrations: ['gmail'], + }, + { + icon: BrexIcon, + title: 'Brex team directory assistant', + prompt: + 'Build an agent that answers questions about company spend and team structure by looking up Brex users, departments, locations, and their expenses on demand.', + modules: ['agent'], + category: 'productivity', + tags: ['automation'], + }, + { + icon: BrexIcon, + title: 'Brex vendor payment tracker', + prompt: + 'Build a workflow that lists Brex vendors and recent transfers, reconciles transfer statuses against expected payments stored in a table, and flags any failed or delayed payments.', + modules: ['workflows', 'tables'], + category: 'operations', + tags: ['automation'], + }, + ], + skills: [ + { + name: 'spend-report', + description: + 'Summarize Brex spend over a period, broken down by category, merchant, and user.', + content: + '# Spend Report\n\nBuild a clear summary of company spend from Brex expenses.\n\n## Steps\n1. List expenses filtered to the requested period using the purchased-at date filters.\n2. Group expenses by merchant category, merchant, and user, totaling billing amounts (amounts are in cents).\n3. Highlight the largest expenses and any with OUT_OF_POLICY status.\n\n## Output\nReturn total spend, a breakdown by category and merchant, the top spenders, and any flagged out-of-policy expenses with dashboard links.', + }, + { + name: 'attach-receipt', + description: 'Upload a receipt file and attach it to the right Brex expense.', + content: + '# Attach a Receipt\n\nGet a receipt onto the correct Brex expense.\n\n## Steps\n1. If the target expense is known, use Upload Receipt with the expense ID.\n2. If not, use Match Receipt so Brex pairs the receipt with the right expense automatically.\n3. Confirm the upload succeeded and capture the receipt ID.\n\n## Output\nReturn the receipt ID, the receipt name, and the expense it was attached to (or note that Brex is matching it automatically).', + }, + { + name: 'memo-cleanup', + description: 'Find Brex expenses missing memos and fill them in from merchant details.', + content: + '# Memo Cleanup\n\nKeep expense memos complete for accounting.\n\n## Steps\n1. List recent expenses and find ones with an empty memo.\n2. For each, draft a short memo from the merchant descriptor, category, and amount.\n3. Update each expense with the drafted memo using Update Expense Memo.\n\n## Output\nReturn the list of expenses updated, each with its new memo, and any expenses that could not be updated.', + }, + { + name: 'budget-utilization', + description: 'Report utilization for Brex budgets and spend limits.', + content: + '# Budget Utilization\n\nShow how much of each budget and spend limit has been used.\n\n## Steps\n1. List budgets and capture each amount and status.\n2. List spend limits and capture each current period balance.\n3. Compute utilization where both an amount and a balance are available (amounts are in cents).\n\n## Output\nReturn each budget and spend limit with its owner, period, amount, and utilization, flagging any that are near or over their limit.', + }, + { + name: 'cash-balance-check', + description: 'Check Brex cash account balances and recent account activity.', + content: + '# Cash Balance Check\n\nGive a quick read on company cash in Brex.\n\n## Steps\n1. List cash accounts and capture current and available balances (amounts are in cents).\n2. For the primary account, list recent cash transactions.\n3. Note any unusually large recent movements.\n\n## Output\nReturn each account with its balances, the most recent transactions for the primary account, and any large movements worth a look.', + }, + { + name: 'statement-reconciliation', + description: 'Reconcile a Brex card statement period against its settled transactions.', + 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.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index bc7d8b42bb..86fd6ececb 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -22,6 +22,7 @@ import { AttioBlock, AttioBlockMeta } from '@/blocks/blocks/attio' import { AzureDevOpsBlock, AzureDevOpsBlockMeta } from '@/blocks/blocks/azure_devops' import { BoxBlock, BoxBlockMeta } from '@/blocks/blocks/box' import { BrandfetchBlock, BrandfetchBlockMeta } from '@/blocks/blocks/brandfetch' +import { BrexBlock, BrexBlockMeta } from '@/blocks/blocks/brex' import { BrightDataBlock, BrightDataBlockMeta } from '@/blocks/blocks/brightdata' import { BrowserUseBlock, BrowserUseBlockMeta } from '@/blocks/blocks/browser_use' import { CalComBlock, CalComBlockMeta } from '@/blocks/blocks/calcom' @@ -345,6 +346,7 @@ const BLOCK_REGISTRY: Record = { azure_devops: AzureDevOpsBlock, box: BoxBlock, brandfetch: BrandfetchBlock, + brex: BrexBlock, brightdata: BrightDataBlock, browser_use: BrowserUseBlock, calcom: CalComBlock, @@ -641,6 +643,7 @@ const BLOCK_META_REGISTRY: Record = { azure_devops: AzureDevOpsBlockMeta, box: BoxBlockMeta, brandfetch: BrandfetchBlockMeta, + brex: BrexBlockMeta, brightdata: BrightDataBlockMeta, browser_use: BrowserUseBlockMeta, calcom: CalComBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6161069bef..90987f14fd 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2261,6 +2261,17 @@ export function BrandfetchIcon(props: SVGProps) { ) } +export function BrexIcon(props: SVGProps) { + return ( + + + + ) +} + export function BrightDataIcon(props: SVGProps) { return ( +export type BrexUploadReceiptRouteResponse = ContractJsonResponse diff --git a/apps/sim/lib/api/contracts/tools/index.ts b/apps/sim/lib/api/contracts/tools/index.ts index b287452fe1..5a09f5095a 100644 --- a/apps/sim/lib/api/contracts/tools/index.ts +++ b/apps/sim/lib/api/contracts/tools/index.ts @@ -1,6 +1,7 @@ export * from './a2a' export * from './agiloft' export * from './asana' +export * from './brex' export * from './communication' export * from './crowdstrike' export * from './cursor' diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 7344cb8ee1..34f8cec180 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -24,6 +24,7 @@ import { BoxCompanyIcon, BrainIcon, BrandfetchIcon, + BrexIcon, BrightDataIcon, BrowserUseIcon, CalComIcon, @@ -241,6 +242,7 @@ export const blockTypeToIconMap: Record = { azure_devops: AzureIcon, box: BoxCompanyIcon, brandfetch: BrandfetchIcon, + brex: BrexIcon, brightdata: BrightDataIcon, browser_use: BrowserUseIcon, calcom: CalComIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 72301855dd..7ea104cafd 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -2167,6 +2167,137 @@ "integrationType": "sales", "tags": ["enrichment", "marketing"] }, + { + "type": "brex", + "slug": "brex", + "name": "Brex", + "description": "Manage expenses, receipts, transactions, and team data in Brex", + "longDescription": "Integrates Brex into the workflow. List and update expenses, upload and match receipts, view card and cash transactions, accounts, budgets, spend limits, vendors, transfers, and team data.", + "bgColor": "#171717", + "iconName": "BrexIcon", + "docsUrl": "https://docs.sim.ai/integrations/brex", + "operations": [ + { + "name": "List Expenses", + "description": "List expenses in the Brex account with optional filters for user, status, payment status, and purchase date range" + }, + { + "name": "Get Expense", + "description": "Get a single Brex expense by its ID, including merchant, user, and receipt details" + }, + { + "name": "Update Expense Memo", + "description": "Update the memo of a Brex card expense" + }, + { + "name": "Upload Receipt", + "description": "Upload a receipt file and attach it to a specific Brex card expense" + }, + { + "name": "Match Receipt", + "description": "Upload a receipt file and let Brex automatically match it with existing expenses" + }, + { + "name": "List Card Transactions", + "description": "List settled card transactions for all Brex card accounts" + }, + { + "name": "List Cash Transactions", + "description": "List transactions for a Brex cash account" + }, + { + "name": "List Card Accounts", + "description": "List all Brex card accounts with balances and limits" + }, + { + "name": "List Cash Accounts", + "description": "List all Brex cash accounts with balances and account details" + }, + { + "name": "Get Cash Account", + "description": "Get a Brex cash account by ID, or the primary cash account when no ID is provided" + }, + { + "name": "List Card Statements", + "description": "List finalized statements for the primary Brex card account" + }, + { + "name": "List Cash Statements", + "description": "List finalized statements for a Brex cash account" + }, + { + "name": "List Users", + "description": "List users in the Brex account, optionally filtered by email" + }, + { + "name": "Get User", + "description": "Get a Brex user by their ID" + }, + { + "name": "Get Current User", + "description": "Get the Brex user associated with the API token" + }, + { + "name": "List Departments", + "description": "List departments in the Brex account, optionally filtered by name" + }, + { + "name": "List Locations", + "description": "List locations in the Brex account, optionally filtered by name" + }, + { + "name": "List Titles", + "description": "List job titles in the Brex account, optionally filtered by name" + }, + { + "name": "List Cards", + "description": "List cards in the Brex account, optionally filtered by card owner" + }, + { + "name": "Get Company", + "description": "Get the Brex company associated with the API token" + }, + { + "name": "List Budgets", + "description": "List budgets in the Brex account" + }, + { + "name": "Get Budget", + "description": "Get a Brex budget by its ID" + }, + { + "name": "List Spend Limits", + "description": "List spend limits in the Brex account, optionally filtered by member user" + }, + { + "name": "Get Spend Limit", + "description": "Get a Brex spend limit by its ID" + }, + { + "name": "List Vendors", + "description": "List vendors in the Brex account, optionally filtered by name" + }, + { + "name": "Get Vendor", + "description": "Get a Brex vendor by its ID" + }, + { + "name": "List Transfers", + "description": "List money transfers in the Brex account" + }, + { + "name": "Get Transfer", + "description": "Get a Brex money transfer by its ID" + } + ], + "operationCount": 28, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "commerce", + "tags": ["payments"] + }, { "type": "brightdata", "slug": "bright-data", diff --git a/apps/sim/tools/brex/get_budget.ts b/apps/sim/tools/brex/get_budget.ts new file mode 100644 index 0000000000..9496eac14c --- /dev/null +++ b/apps/sim/tools/brex/get_budget.ts @@ -0,0 +1,79 @@ +import type { BrexGetBudgetParams, BrexGetBudgetResponse } 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 brexGetBudgetTool: ToolConfig = { + id: 'brex_get_budget', + name: 'Brex Get Budget', + description: 'Get a Brex budget by its ID', + 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 fetch', + }, + }, + + request: { + url: (params) => `${BREX_API_BASE}/v2/budgets/${encodeURIComponent(params.budgetId.trim())}`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + 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: 'Budget status (ACTIVE, ARCHIVED, DELETED, EXPIRED)', + }, + limitType: { type: 'string', description: 'Budget limit type (HARD or SOFT)', optional: true }, + }, +} diff --git a/apps/sim/tools/brex/get_cash_account.ts b/apps/sim/tools/brex/get_cash_account.ts new file mode 100644 index 0000000000..1063a10236 --- /dev/null +++ b/apps/sim/tools/brex/get_cash_account.ts @@ -0,0 +1,76 @@ +import type { BrexGetCashAccountParams, BrexGetCashAccountResponse } 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 brexGetCashAccountTool: ToolConfig< + BrexGetCashAccountParams, + BrexGetCashAccountResponse +> = { + id: 'brex_get_cash_account', + name: 'Brex Get Cash Account', + description: 'Get a Brex cash account by ID, or the primary cash account when no ID is provided', + 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)', + }, + accountId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ID of the cash account (defaults to the primary cash account)', + }, + }, + + request: { + url: (params) => { + const accountId = params.accountId?.trim() + return accountId + ? `${BREX_API_BASE}/v2/accounts/cash/${encodeURIComponent(accountId)}` + : `${BREX_API_BASE}/v2/accounts/cash/primary` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + status: data.status ?? null, + currentBalance: data.current_balance, + availableBalance: data.available_balance, + accountNumber: data.account_number ?? '', + routingNumber: data.routing_number ?? '', + primary: data.primary ?? false, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique account ID' }, + name: { type: 'string', description: 'Account name' }, + status: { type: 'string', description: 'Account status', optional: true }, + currentBalance: { + type: 'json', + description: 'Current balance', + properties: BREX_MONEY_PROPERTIES, + }, + availableBalance: { + type: 'json', + description: 'Available balance', + properties: BREX_MONEY_PROPERTIES, + }, + accountNumber: { type: 'string', description: 'Bank account number' }, + routingNumber: { type: 'string', description: 'Bank routing number' }, + primary: { type: 'boolean', description: 'Whether this is the primary cash account' }, + }, +} diff --git a/apps/sim/tools/brex/get_company.ts b/apps/sim/tools/brex/get_company.ts new file mode 100644 index 0000000000..700e339a08 --- /dev/null +++ b/apps/sim/tools/brex/get_company.ts @@ -0,0 +1,53 @@ +import type { BrexApiKeyParams, BrexGetCompanyResponse } from '@/tools/brex/types' +import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexGetCompanyTool: ToolConfig = { + id: 'brex_get_company', + name: 'Brex Get Company', + description: 'Get the Brex company associated with the API token', + 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)', + }, + }, + + request: { + url: `${BREX_API_BASE}/v2/company`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + legalName: data.legal_name ?? '', + mailingAddress: data.mailing_address ?? null, + accountType: data.accountType ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique company ID' }, + legalName: { type: 'string', description: 'Legal name of the company' }, + mailingAddress: { + type: 'json', + description: 'Company mailing address (line1, line2, city, state, country, postal_code)', + optional: true, + }, + accountType: { + type: 'string', + description: 'Brex account type (BREX_CLASSIC or BREX_EMPOWER)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/get_current_user.ts b/apps/sim/tools/brex/get_current_user.ts new file mode 100644 index 0000000000..169ca3e23b --- /dev/null +++ b/apps/sim/tools/brex/get_current_user.ts @@ -0,0 +1,60 @@ +import type { BrexApiKeyParams, BrexGetUserResponse } from '@/tools/brex/types' +import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexGetCurrentUserTool: ToolConfig = { + id: 'brex_get_current_user', + name: 'Brex Get Current User', + description: 'Get the Brex user associated with the API token', + 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)', + }, + }, + + request: { + url: `${BREX_API_BASE}/v2/users/me`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + firstName: data.first_name ?? '', + lastName: data.last_name ?? '', + email: data.email ?? '', + status: data.status ?? null, + managerId: data.manager_id ?? null, + departmentId: data.department_id ?? null, + locationId: data.location_id ?? null, + titleId: data.title_id ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique user ID' }, + firstName: { type: 'string', description: 'First name' }, + lastName: { type: 'string', description: 'Last name' }, + email: { type: 'string', description: 'Email address' }, + status: { + type: 'string', + description: + 'User status (INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED)', + optional: true, + }, + managerId: { type: 'string', description: 'ID of the manager', optional: true }, + departmentId: { type: 'string', description: 'Department ID', optional: true }, + locationId: { type: 'string', description: 'Location ID', optional: true }, + titleId: { type: 'string', description: 'Title ID', optional: true }, + }, +} diff --git a/apps/sim/tools/brex/get_expense.ts b/apps/sim/tools/brex/get_expense.ts new file mode 100644 index 0000000000..27f80592fb --- /dev/null +++ b/apps/sim/tools/brex/get_expense.ts @@ -0,0 +1,204 @@ +import type { BrexGetExpenseParams, BrexGetExpenseResponse } 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' + +const EXPAND_FIELDS = [ + 'merchant', + 'user', + 'budget', + 'department', + 'location', + 'receipts.download_uris', +] + +export const brexGetExpenseTool: ToolConfig = { + id: 'brex_get_expense', + name: 'Brex Get Expense', + description: 'Get a single Brex expense by its ID, including merchant, user, and receipt details', + 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)', + }, + expenseId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the expense to fetch', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + for (const field of EXPAND_FIELDS) { + query.append('expand[]', field) + } + return `${BREX_API_BASE}/v1/expenses/${encodeURIComponent(params.expenseId.trim())}?${query.toString()}` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + memo: data.memo ?? null, + status: data.status ?? null, + paymentStatus: data.payment_status ?? null, + expenseType: data.expense_type ?? null, + category: data.category ?? null, + merchantId: data.merchant_id ?? null, + merchant: data.merchant ?? null, + budgetId: data.budget_id ?? null, + budget: data.budget ?? null, + departmentId: data.department_id ?? null, + department: data.department ?? null, + locationId: data.location_id ?? null, + location: data.location ?? null, + userId: data.user_id ?? null, + user: data.user ?? null, + originalAmount: data.original_amount ?? null, + billingAmount: data.billing_amount ?? null, + purchasedAmount: data.purchased_amount ?? null, + usdEquivalentAmount: data.usd_equivalent_amount ?? null, + purchasedAt: data.purchased_at ?? null, + updatedAt: data.updated_at ?? '', + paymentPostedAt: data.payment_posted_at ?? null, + receipts: data.receipts ?? [], + dashboardUrl: data.dashboard_url ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique expense ID' }, + memo: { type: 'string', description: 'Memo on the expense', optional: true }, + status: { + type: 'string', + description: + 'Expense status (DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED)', + optional: true, + }, + paymentStatus: { + type: 'string', + description: + 'Payment status (NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED)', + optional: true, + }, + expenseType: { + type: 'string', + description: 'Expense type (CARD, BILLPAY, REIMBURSEMENT, CLAWBACK, UNSET)', + optional: true, + }, + category: { + type: 'string', + description: + 'Expense category (e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES)', + optional: true, + }, + merchantId: { type: 'string', description: 'Merchant ID', optional: true }, + merchant: { + type: 'json', + description: 'Merchant details (raw descriptor, MCC, country)', + optional: true, + properties: { + raw_descriptor: { type: 'string', description: 'Raw merchant descriptor' }, + mcc: { type: 'string', description: 'Merchant category code' }, + country: { type: 'string', description: 'Merchant country' }, + }, + }, + budgetId: { type: 'string', description: 'Budget ID', optional: true }, + budget: { + type: 'json', + description: 'Budget the expense belongs to', + optional: true, + properties: { + id: { type: 'string', description: 'Budget ID' }, + name: { type: 'string', description: 'Budget name' }, + }, + }, + departmentId: { type: 'string', description: 'Department ID', optional: true }, + department: { + type: 'json', + description: 'Department of the expense owner', + optional: true, + properties: { + id: { type: 'string', description: 'Department ID' }, + name: { type: 'string', description: 'Department name' }, + }, + }, + locationId: { type: 'string', description: 'Location ID', optional: true }, + location: { + type: 'json', + description: 'Location of the expense owner', + optional: true, + properties: { + id: { type: 'string', description: 'Location ID' }, + name: { type: 'string', description: 'Location name' }, + }, + }, + userId: { type: 'string', description: 'ID of the user who made the expense', optional: true }, + user: { + type: 'json', + description: 'User who made the expense', + optional: true, + properties: { + id: { type: 'string', description: 'User ID' }, + first_name: { type: 'string', description: 'First name' }, + last_name: { type: 'string', description: 'Last name' }, + }, + }, + originalAmount: { + type: 'json', + description: 'Original transaction amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + billingAmount: { + type: 'json', + description: 'Amount billed to the account', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + purchasedAmount: { + type: 'json', + description: 'Amount at the time of purchase', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + usdEquivalentAmount: { + type: 'json', + description: 'USD equivalent amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + purchasedAt: { type: 'string', description: 'Purchase timestamp (ISO 8601)', optional: true }, + updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' }, + paymentPostedAt: { + type: 'string', + description: 'Timestamp the payment was posted (ISO 8601)', + optional: true, + }, + receipts: { + type: 'array', + description: 'Receipts attached to the expense', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Receipt ID' }, + download_uris: { type: 'array', description: 'Pre-signed receipt download URLs' }, + }, + }, + }, + dashboardUrl: { type: 'string', description: 'Link to the expense in the Brex dashboard' }, + }, +} diff --git a/apps/sim/tools/brex/get_spend_limit.ts b/apps/sim/tools/brex/get_spend_limit.ts new file mode 100644 index 0000000000..2387b44dea --- /dev/null +++ b/apps/sim/tools/brex/get_spend_limit.ts @@ -0,0 +1,89 @@ +import type { BrexGetSpendLimitParams, BrexGetSpendLimitResponse } from '@/tools/brex/types' +import { BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES } from '@/tools/brex/types' +import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexGetSpendLimitTool: ToolConfig = + { + id: 'brex_get_spend_limit', + name: 'Brex Get Spend Limit', + description: 'Get a Brex spend limit by its ID', + 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)', + }, + spendLimitId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the spend limit to fetch', + }, + }, + + request: { + url: (params) => + `${BREX_API_BASE}/v2/spend_limits/${encodeURIComponent(params.spendLimitId.trim())}`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + 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 (ACTIVE, EXPIRED, ARCHIVED, DELETED)', + }, + 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, + }, + }, + } diff --git a/apps/sim/tools/brex/get_transfer.ts b/apps/sim/tools/brex/get_transfer.ts new file mode 100644 index 0000000000..dcbd9f0233 --- /dev/null +++ b/apps/sim/tools/brex/get_transfer.ts @@ -0,0 +1,101 @@ +import type { BrexGetTransferParams, BrexGetTransferResponse } 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 brexGetTransferTool: ToolConfig = { + id: 'brex_get_transfer', + name: 'Brex Get Transfer', + description: 'Get a Brex money transfer by its ID', + 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)', + }, + transferId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the transfer to fetch', + }, + }, + + request: { + url: (params) => + `${BREX_API_BASE}/v1/transfers/${encodeURIComponent(params.transferId.trim())}`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + 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, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique transfer ID' }, + counterparty: { type: 'json', description: 'Transfer counterparty details', optional: true }, + description: { type: 'string', description: 'Transfer description', 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: 'Date the transfer processes', optional: true }, + originatingAccount: { + type: 'json', + description: 'Account the transfer originates from', + 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', + optional: true, + }, + creatorUserId: { + type: 'string', + description: 'ID of the user who created the transfer', + optional: true, + }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + displayName: { type: 'string', description: 'Transfer display name', optional: true }, + externalMemo: { type: 'string', description: 'External memo', optional: true }, + }, +} diff --git a/apps/sim/tools/brex/get_user.ts b/apps/sim/tools/brex/get_user.ts new file mode 100644 index 0000000000..f48e270752 --- /dev/null +++ b/apps/sim/tools/brex/get_user.ts @@ -0,0 +1,66 @@ +import type { BrexGetUserParams, BrexGetUserResponse } from '@/tools/brex/types' +import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexGetUserTool: ToolConfig = { + id: 'brex_get_user', + name: 'Brex Get User', + description: 'Get a Brex user by their ID', + 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)', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the user to fetch', + }, + }, + + request: { + url: (params) => `${BREX_API_BASE}/v2/users/${encodeURIComponent(params.userId.trim())}`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + firstName: data.first_name ?? '', + lastName: data.last_name ?? '', + email: data.email ?? '', + status: data.status ?? null, + managerId: data.manager_id ?? null, + departmentId: data.department_id ?? null, + locationId: data.location_id ?? null, + titleId: data.title_id ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique user ID' }, + firstName: { type: 'string', description: 'First name' }, + lastName: { type: 'string', description: 'Last name' }, + email: { type: 'string', description: 'Email address' }, + status: { + type: 'string', + description: + 'User status (INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED)', + optional: true, + }, + managerId: { type: 'string', description: 'ID of the manager', optional: true }, + departmentId: { type: 'string', description: 'Department ID', optional: true }, + locationId: { type: 'string', description: 'Location ID', optional: true }, + titleId: { type: 'string', description: 'Title ID', optional: true }, + }, +} diff --git a/apps/sim/tools/brex/get_vendor.ts b/apps/sim/tools/brex/get_vendor.ts new file mode 100644 index 0000000000..ad13edfbee --- /dev/null +++ b/apps/sim/tools/brex/get_vendor.ts @@ -0,0 +1,56 @@ +import type { BrexGetVendorParams, BrexGetVendorResponse } from '@/tools/brex/types' +import { BREX_API_BASE, buildBrexHeaders, parseBrexJson } from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexGetVendorTool: ToolConfig = { + id: 'brex_get_vendor', + name: 'Brex Get Vendor', + description: 'Get a Brex vendor by its ID', + 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 fetch', + }, + }, + + request: { + url: (params) => `${BREX_API_BASE}/v1/vendors/${encodeURIComponent(params.vendorId.trim())}`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + 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', + }, + }, +} diff --git a/apps/sim/tools/brex/index.ts b/apps/sim/tools/brex/index.ts new file mode 100644 index 0000000000..4bc1194ee0 --- /dev/null +++ b/apps/sim/tools/brex/index.ts @@ -0,0 +1,28 @@ +export { brexGetBudgetTool } from '@/tools/brex/get_budget' +export { brexGetCashAccountTool } from '@/tools/brex/get_cash_account' +export { brexGetCompanyTool } from '@/tools/brex/get_company' +export { brexGetCurrentUserTool } from '@/tools/brex/get_current_user' +export { brexGetExpenseTool } from '@/tools/brex/get_expense' +export { brexGetSpendLimitTool } from '@/tools/brex/get_spend_limit' +export { brexGetTransferTool } from '@/tools/brex/get_transfer' +export { brexGetUserTool } from '@/tools/brex/get_user' +export { brexGetVendorTool } from '@/tools/brex/get_vendor' +export { brexListBudgetsTool } from '@/tools/brex/list_budgets' +export { brexListCardAccountsTool } from '@/tools/brex/list_card_accounts' +export { brexListCardStatementsTool } from '@/tools/brex/list_card_statements' +export { brexListCardTransactionsTool } from '@/tools/brex/list_card_transactions' +export { brexListCardsTool } from '@/tools/brex/list_cards' +export { brexListCashAccountsTool } from '@/tools/brex/list_cash_accounts' +export { brexListCashStatementsTool } from '@/tools/brex/list_cash_statements' +export { brexListCashTransactionsTool } from '@/tools/brex/list_cash_transactions' +export { brexListDepartmentsTool } from '@/tools/brex/list_departments' +export { brexListExpensesTool } from '@/tools/brex/list_expenses' +export { brexListLocationsTool } from '@/tools/brex/list_locations' +export { brexListSpendLimitsTool } from '@/tools/brex/list_spend_limits' +export { brexListTitlesTool } from '@/tools/brex/list_titles' +export { brexListTransfersTool } from '@/tools/brex/list_transfers' +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 { brexUploadReceiptTool } from '@/tools/brex/upload_receipt' diff --git a/apps/sim/tools/brex/list_budgets.ts b/apps/sim/tools/brex/list_budgets.ts new file mode 100644 index 0000000000..a1d2eab886 --- /dev/null +++ b/apps/sim/tools/brex/list_budgets.ts @@ -0,0 +1,98 @@ +import type { BrexListBudgetsResponse, BrexPaginationParams } from '@/tools/brex/types' +import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListBudgetsTool: ToolConfig = { + id: 'brex_list_budgets', + name: 'Brex List Budgets', + description: 'List budgets 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)', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of budgets to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/budgets?${queryString}` + : `${BREX_API_BASE}/v2/budgets` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Budgets in the Brex account', + items: { + type: 'json', + properties: { + budget_id: { type: 'string', description: 'Unique budget ID' }, + account_id: { type: 'string', description: 'Account ID the budget belongs to' }, + name: { type: 'string', description: 'Budget name' }, + description: { type: 'string', description: 'Budget description', optional: true }, + parent_budget_id: { type: 'string', description: 'Parent budget ID', optional: true }, + owner_user_ids: { type: 'array', description: 'User IDs of the budget owners' }, + period_recurrence_type: { + type: 'string', + description: 'Budget period recurrence (WEEKLY, MONTHLY, QUARTERLY, YEARLY, ONE_TIME)', + }, + start_date: { type: 'string', description: 'Budget start date', optional: true }, + end_date: { type: 'string', description: 'Budget end date', optional: true }, + amount: { + type: 'json', + description: 'Budget amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + spend_budget_status: { type: 'string', description: 'Budget status' }, + limit_type: { type: 'string', description: 'Budget limit type', optional: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_card_accounts.ts b/apps/sim/tools/brex/list_card_accounts.ts new file mode 100644 index 0000000000..24c336546f --- /dev/null +++ b/apps/sim/tools/brex/list_card_accounts.ts @@ -0,0 +1,73 @@ +import type { BrexApiKeyParams, BrexListCardAccountsResponse } 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 brexListCardAccountsTool: ToolConfig = + { + id: 'brex_list_card_accounts', + name: 'Brex List Card Accounts', + description: 'List all Brex card accounts with balances and limits', + 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)', + }, + }, + + request: { + url: `${BREX_API_BASE}/v2/accounts/card`, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + accounts: Array.isArray(data) ? data : [], + }, + } + }, + + outputs: { + accounts: { + type: 'array', + description: 'Card accounts', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique account ID' }, + status: { type: 'string', description: 'Account status', optional: true }, + current_balance: { + type: 'json', + description: 'Current balance', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + available_balance: { + type: 'json', + description: 'Available balance', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + account_limit: { + type: 'json', + description: 'Account limit', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + current_statement_period: { + type: 'json', + description: 'Current statement period (start_date, end_date)', + }, + }, + }, + }, + }, + } diff --git a/apps/sim/tools/brex/list_card_statements.ts b/apps/sim/tools/brex/list_card_statements.ts new file mode 100644 index 0000000000..dfe9308ed4 --- /dev/null +++ b/apps/sim/tools/brex/list_card_statements.ts @@ -0,0 +1,95 @@ +import type { BrexListStatementsResponse, BrexPaginationParams } from '@/tools/brex/types' +import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCardStatementsTool: ToolConfig< + BrexPaginationParams, + BrexListStatementsResponse +> = { + id: 'brex_list_card_statements', + name: 'Brex List Card Statements', + description: 'List finalized statements for the primary Brex card 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)', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of statements to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/accounts/card/primary/statements?${queryString}` + : `${BREX_API_BASE}/v2/accounts/card/primary/statements` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Finalized card account statements', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique statement ID' }, + start_balance: { + type: 'json', + description: 'Balance at the start of the period', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + end_balance: { + type: 'json', + description: 'Balance at the end of the period', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + period: { type: 'json', description: 'Statement period (start_date, end_date)' }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_card_transactions.ts b/apps/sim/tools/brex/list_card_transactions.ts new file mode 100644 index 0000000000..d986c9c42e --- /dev/null +++ b/apps/sim/tools/brex/list_card_transactions.ts @@ -0,0 +1,95 @@ +import type { + BrexListCardTransactionsParams, + BrexListCardTransactionsResponse, +} from '@/tools/brex/types' +import { BREX_CARD_TRANSACTION_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexArrayParam, + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, + toBrexDateTime, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCardTransactionsTool: ToolConfig< + BrexListCardTransactionsParams, + BrexListCardTransactionsResponse +> = { + id: 'brex_list_card_transactions', + name: 'Brex List Card Transactions', + description: 'List settled card transactions for all Brex card accounts', + 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)', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user IDs to filter transactions by cardholder', + }, + postedAtStart: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions posted at or after this ISO 8601 timestamp', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of transactions to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + query.append('expand[]', 'expense_id') + appendBrexArrayParam(query, 'user_ids', params.userIds) + if (params.postedAtStart) + query.append('posted_at_start', toBrexDateTime(params.postedAtStart)) + appendBrexPagination(query, params) + return `${BREX_API_BASE}/v2/transactions/card/primary?${query.toString()}` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Settled card transactions', + items: { type: 'json', properties: BREX_CARD_TRANSACTION_PROPERTIES }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_cards.ts b/apps/sim/tools/brex/list_cards.ts new file mode 100644 index 0000000000..0c05121d18 --- /dev/null +++ b/apps/sim/tools/brex/list_cards.ts @@ -0,0 +1,101 @@ +import type { BrexListCardsParams, BrexListCardsResponse } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCardsTool: ToolConfig = { + id: 'brex_list_cards', + name: 'Brex List Cards', + description: 'List cards in the Brex account, optionally filtered by card owner', + 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)', + }, + userId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter cards by the ID of the card owner', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of cards to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.userId) query.append('user_id', params.userId.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString ? `${BREX_API_BASE}/v2/cards?${queryString}` : `${BREX_API_BASE}/v2/cards` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Cards in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique card ID' }, + owner: { type: 'json', description: 'Card owner (type, user_id)' }, + status: { type: 'string', description: 'Card status', optional: true }, + last_four: { type: 'string', description: 'Last four digits of the card number' }, + card_name: { type: 'string', description: 'Card name' }, + card_type: { + type: 'string', + description: 'Card type (VIRTUAL or PHYSICAL)', + optional: true, + }, + limit_type: { type: 'string', description: 'Limit type (CARD or USER)' }, + spend_controls: { + type: 'json', + description: 'Spend controls on the card', + optional: true, + }, + billing_address: { type: 'json', description: 'Billing address of the card' }, + expiration_date: { type: 'json', description: 'Card expiration date (month, year)' }, + budget_id: { type: 'string', description: 'Associated budget ID', optional: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_cash_accounts.ts b/apps/sim/tools/brex/list_cash_accounts.ts new file mode 100644 index 0000000000..1240abce3c --- /dev/null +++ b/apps/sim/tools/brex/list_cash_accounts.ts @@ -0,0 +1,97 @@ +import type { BrexListCashAccountsResponse, BrexPaginationParams } from '@/tools/brex/types' +import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCashAccountsTool: ToolConfig< + BrexPaginationParams, + BrexListCashAccountsResponse +> = { + id: 'brex_list_cash_accounts', + name: 'Brex List Cash Accounts', + description: 'List all Brex cash accounts with balances and account details', + 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)', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of accounts to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/accounts/cash?${queryString}` + : `${BREX_API_BASE}/v2/accounts/cash` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Cash accounts', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique account ID' }, + name: { type: 'string', description: 'Account name' }, + status: { type: 'string', description: 'Account status', optional: true }, + current_balance: { + type: 'json', + description: 'Current balance', + properties: BREX_MONEY_PROPERTIES, + }, + available_balance: { + type: 'json', + description: 'Available balance', + properties: BREX_MONEY_PROPERTIES, + }, + account_number: { type: 'string', description: 'Bank account number' }, + routing_number: { type: 'string', description: 'Bank routing number' }, + primary: { type: 'boolean', description: 'Whether this is the primary cash account' }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_cash_statements.ts b/apps/sim/tools/brex/list_cash_statements.ts new file mode 100644 index 0000000000..bb506b446a --- /dev/null +++ b/apps/sim/tools/brex/list_cash_statements.ts @@ -0,0 +1,100 @@ +import type { BrexListCashStatementsParams, BrexListStatementsResponse } from '@/tools/brex/types' +import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCashStatementsTool: ToolConfig< + BrexListCashStatementsParams, + BrexListStatementsResponse +> = { + id: 'brex_list_cash_statements', + name: 'Brex List Cash Statements', + description: 'List finalized statements for a Brex cash 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)', + }, + accountId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the cash account to list statements for', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of statements to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexPagination(query, params) + const queryString = query.toString() + const base = `${BREX_API_BASE}/v2/accounts/cash/${encodeURIComponent(params.accountId.trim())}/statements` + return queryString ? `${base}?${queryString}` : base + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Finalized cash account statements', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique statement ID' }, + start_balance: { + type: 'json', + description: 'Balance at the start of the period', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + end_balance: { + type: 'json', + description: 'Balance at the end of the period', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + period: { type: 'json', description: 'Statement period (start_date, end_date)' }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_cash_transactions.ts b/apps/sim/tools/brex/list_cash_transactions.ts new file mode 100644 index 0000000000..5c10209941 --- /dev/null +++ b/apps/sim/tools/brex/list_cash_transactions.ts @@ -0,0 +1,94 @@ +import type { + BrexListCashTransactionsParams, + BrexListCashTransactionsResponse, +} from '@/tools/brex/types' +import { BREX_CASH_TRANSACTION_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, + toBrexDateTime, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListCashTransactionsTool: ToolConfig< + BrexListCashTransactionsParams, + BrexListCashTransactionsResponse +> = { + id: 'brex_list_cash_transactions', + name: 'Brex List Cash Transactions', + description: 'List transactions for a Brex cash 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)', + }, + accountId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the cash account to list transactions for', + }, + postedAtStart: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions posted at or after this ISO 8601 timestamp', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of transactions to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.postedAtStart) + query.append('posted_at_start', toBrexDateTime(params.postedAtStart)) + appendBrexPagination(query, params) + const queryString = query.toString() + const base = `${BREX_API_BASE}/v2/transactions/cash/${encodeURIComponent(params.accountId.trim())}` + return queryString ? `${base}?${queryString}` : base + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Cash account transactions', + items: { type: 'json', properties: BREX_CASH_TRANSACTION_PROPERTIES }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_departments.ts b/apps/sim/tools/brex/list_departments.ts new file mode 100644 index 0000000000..ce8916326f --- /dev/null +++ b/apps/sim/tools/brex/list_departments.ts @@ -0,0 +1,90 @@ +import type { BrexListDepartmentsResponse, BrexNameFilterParams } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListDepartmentsTool: ToolConfig< + BrexNameFilterParams, + BrexListDepartmentsResponse +> = { + id: 'brex_list_departments', + name: 'Brex List Departments', + description: 'List departments in the Brex account, optionally filtered by name', + 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: false, + visibility: 'user-or-llm', + description: 'Filter departments by name', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of departments to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.name) query.append('name', params.name.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/departments?${queryString}` + : `${BREX_API_BASE}/v2/departments` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Departments in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique department ID' }, + name: { type: 'string', description: 'Department name' }, + description: { type: 'string', description: 'Department description', optional: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_expenses.ts b/apps/sim/tools/brex/list_expenses.ts new file mode 100644 index 0000000000..03e1e64899 --- /dev/null +++ b/apps/sim/tools/brex/list_expenses.ts @@ -0,0 +1,122 @@ +import type { BrexListExpensesParams, BrexListExpensesResponse } from '@/tools/brex/types' +import { BREX_EXPENSE_ITEM_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexArrayParam, + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +const EXPAND_FIELDS = [ + 'merchant', + 'user', + 'budget', + 'department', + 'location', + 'receipts.download_uris', +] + +export const brexListExpensesTool: ToolConfig = { + id: 'brex_list_expenses', + name: 'Brex List Expenses', + description: + 'List expenses in the Brex account with optional filters for user, status, payment status, and purchase date range', + 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)', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user IDs to filter expenses by owner', + }, + statuses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated expense statuses to filter by: DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED', + }, + paymentStatuses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated payment statuses to filter by: NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED', + }, + purchasedAtStart: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include expenses purchased at or after this ISO 8601 timestamp', + }, + purchasedAtEnd: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include expenses purchased at or before this ISO 8601 timestamp', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of expenses to return (max 100)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + for (const field of EXPAND_FIELDS) { + query.append('expand[]', field) + } + 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) + appendBrexPagination(query, params) + return `${BREX_API_BASE}/v1/expenses?${query.toString()}` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Expenses matching the filters', + items: { type: 'json', properties: BREX_EXPENSE_ITEM_PROPERTIES }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_locations.ts b/apps/sim/tools/brex/list_locations.ts new file mode 100644 index 0000000000..1d8219b7ce --- /dev/null +++ b/apps/sim/tools/brex/list_locations.ts @@ -0,0 +1,87 @@ +import type { BrexListLocationsResponse, BrexNameFilterParams } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListLocationsTool: ToolConfig = { + id: 'brex_list_locations', + name: 'Brex List Locations', + description: 'List locations in the Brex account, optionally filtered by name', + 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: false, + visibility: 'user-or-llm', + description: 'Filter locations by name', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of locations to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.name) query.append('name', params.name.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/locations?${queryString}` + : `${BREX_API_BASE}/v2/locations` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Locations in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique location ID' }, + name: { type: 'string', description: 'Location name' }, + description: { type: 'string', description: 'Location description', optional: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_spend_limits.ts b/apps/sim/tools/brex/list_spend_limits.ts new file mode 100644 index 0000000000..d8a745c608 --- /dev/null +++ b/apps/sim/tools/brex/list_spend_limits.ts @@ -0,0 +1,108 @@ +import type { BrexListSpendLimitsParams, BrexListSpendLimitsResponse } from '@/tools/brex/types' +import { BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexArrayParam, + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListSpendLimitsTool: ToolConfig< + BrexListSpendLimitsParams, + BrexListSpendLimitsResponse +> = { + id: 'brex_list_spend_limits', + name: 'Brex List Spend Limits', + description: 'List spend limits in the Brex account, optionally filtered by member user', + 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)', + }, + memberUserIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user IDs to filter spend limits by member', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of spend limits to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexArrayParam(query, 'member_user_id[]', params.memberUserIds) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/spend_limits?${queryString}` + : `${BREX_API_BASE}/v2/spend_limits` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Spend limits in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique spend limit ID' }, + account_id: { 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 }, + parent_budget_id: { type: 'string', description: 'Parent budget ID', optional: true }, + status: { type: 'string', description: 'Spend limit status' }, + period_recurrence_type: { + type: 'string', + description: 'Period recurrence (PER_WEEK, PER_MONTH, PER_QUARTER, PER_YEAR, ONE_TIME)', + }, + spend_type: { type: 'string', description: 'Spend type of the limit' }, + owner_user_ids: { type: 'array', description: 'User IDs of the spend limit owners' }, + member_user_ids: { type: 'array', description: 'User IDs of the spend limit members' }, + current_period_balance: { + type: 'json', + description: 'Spend and rollover amounts for the current period', + optional: true, + properties: BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES, + }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_titles.ts b/apps/sim/tools/brex/list_titles.ts new file mode 100644 index 0000000000..94d5025310 --- /dev/null +++ b/apps/sim/tools/brex/list_titles.ts @@ -0,0 +1,86 @@ +import type { BrexListTitlesResponse, BrexNameFilterParams } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListTitlesTool: ToolConfig = { + id: 'brex_list_titles', + name: 'Brex List Titles', + description: 'List job titles in the Brex account, optionally filtered by name', + 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: false, + visibility: 'user-or-llm', + description: 'Filter titles by name', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of titles to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.name) query.append('name', params.name.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v2/titles?${queryString}` + : `${BREX_API_BASE}/v2/titles` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Job titles in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique title ID' }, + name: { type: 'string', description: 'Title name' }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_transfers.ts b/apps/sim/tools/brex/list_transfers.ts new file mode 100644 index 0000000000..12a387425d --- /dev/null +++ b/apps/sim/tools/brex/list_transfers.ts @@ -0,0 +1,127 @@ +import type { BrexListTransfersResponse, BrexPaginationParams } from '@/tools/brex/types' +import { BREX_MONEY_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListTransfersTool: ToolConfig = { + id: 'brex_list_transfers', + name: 'Brex List Transfers', + description: 'List money transfers 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)', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of transfers to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v1/transfers?${queryString}` + : `${BREX_API_BASE}/v1/transfers` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Transfers in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique transfer ID' }, + counterparty: { + type: 'json', + description: 'Transfer counterparty details', + optional: true, + }, + description: { type: 'string', description: 'Transfer description', optional: true }, + payment_type: { + type: 'string', + description: + 'Payment type (ACH, DOMESTIC_WIRE, CHEQUE, INTERNATIONAL_WIRE, BOOK_TRANSFER, STABLECOIN)', + }, + amount: { + type: 'json', + description: 'Transfer amount', + properties: BREX_MONEY_PROPERTIES, + }, + process_date: { + type: 'string', + description: 'Date the transfer processes', + optional: true, + }, + originating_account: { + type: 'json', + description: 'Account the transfer originates from', + }, + status: { + type: 'string', + description: + 'Transfer status (PROCESSING, SCHEDULED, PENDING_APPROVAL, FAILED, PROCESSED)', + }, + cancellation_reason: { + type: 'string', + description: 'Reason the transfer was canceled', + optional: true, + }, + estimated_delivery_date: { + type: 'string', + description: 'Estimated delivery date', + optional: true, + }, + creator_user_id: { + type: 'string', + description: 'ID of the user who created the transfer', + optional: true, + }, + created_at: { type: 'string', description: 'Creation timestamp', optional: true }, + display_name: { type: 'string', description: 'Transfer display name', optional: true }, + external_memo: { type: 'string', description: 'External memo', optional: true }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_users.ts b/apps/sim/tools/brex/list_users.ts new file mode 100644 index 0000000000..1c917a730c --- /dev/null +++ b/apps/sim/tools/brex/list_users.ts @@ -0,0 +1,79 @@ +import type { BrexListUsersParams, BrexListUsersResponse } from '@/tools/brex/types' +import { BREX_USER_PROPERTIES } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListUsersTool: ToolConfig = { + id: 'brex_list_users', + name: 'Brex List Users', + description: 'List users in the Brex account, optionally filtered by email', + 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)', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter users by exact email address', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of users to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.email) query.append('email', params.email.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString ? `${BREX_API_BASE}/v2/users?${queryString}` : `${BREX_API_BASE}/v2/users` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Users in the Brex account', + items: { type: 'json', properties: BREX_USER_PROPERTIES }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/list_vendors.ts b/apps/sim/tools/brex/list_vendors.ts new file mode 100644 index 0000000000..5508f039ca --- /dev/null +++ b/apps/sim/tools/brex/list_vendors.ts @@ -0,0 +1,93 @@ +import type { BrexListVendorsResponse, BrexNameFilterParams } from '@/tools/brex/types' +import { + appendBrexPagination, + BREX_API_BASE, + buildBrexHeaders, + parseBrexJson, +} from '@/tools/brex/utils' +import type { ToolConfig } from '@/tools/types' + +export const brexListVendorsTool: ToolConfig = { + id: 'brex_list_vendors', + name: 'Brex List Vendors', + description: 'List vendors in the Brex account, optionally filtered by name', + 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: false, + visibility: 'user-or-llm', + description: 'Filter vendors by name', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous response', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of vendors to return (default 100, max 1000)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.name) query.append('name', params.name.trim()) + appendBrexPagination(query, params) + const queryString = query.toString() + return queryString + ? `${BREX_API_BASE}/v1/vendors?${queryString}` + : `${BREX_API_BASE}/v1/vendors` + }, + method: 'GET', + headers: (params) => buildBrexHeaders(params.apiKey), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + items: data.items ?? [], + nextCursor: data.next_cursor ?? null, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'Vendors in the Brex account', + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Unique vendor ID' }, + company_name: { 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 }, + payment_accounts: { + type: 'array', + description: 'Payment accounts associated with the vendor', + optional: true, + }, + }, + }, + }, + nextCursor: { + type: 'string', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/match_receipt.ts b/apps/sim/tools/brex/match_receipt.ts new file mode 100644 index 0000000000..621cb5e6b7 --- /dev/null +++ b/apps/sim/tools/brex/match_receipt.ts @@ -0,0 +1,62 @@ +import type { BrexMatchReceiptParams, BrexUploadReceiptResponse } from '@/tools/brex/types' +import type { ToolConfig } from '@/tools/types' + +export const brexMatchReceiptTool: ToolConfig = { + id: 'brex_match_receipt', + name: 'Brex Match Receipt', + description: 'Upload a receipt file and let Brex automatically match it with existing expenses', + 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)', + }, + file: { + type: 'file', + required: true, + visibility: 'user-or-llm', + description: 'Receipt file to upload (max 50 MB)', + }, + receiptName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Receipt file name including extension (defaults to the uploaded file name)', + }, + }, + + request: { + url: '/api/tools/brex/upload-receipt', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + apiKey: params.apiKey, + file: params.file, + receiptName: params.receiptName, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + if (!data.success) { + throw new Error(data.error || 'Failed to match receipt') + } + return { + success: true, + output: data.output, + } + }, + + outputs: { + receiptId: { type: 'string', description: 'Unique identifier of the receipt match request' }, + receiptName: { type: 'string', description: 'Name the receipt was uploaded with' }, + expenseId: { + type: 'string', + description: 'Always null for receipt match (Brex matches the receipt asynchronously)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/brex/types.ts b/apps/sim/tools/brex/types.ts new file mode 100644 index 0000000000..3606a34b50 --- /dev/null +++ b/apps/sim/tools/brex/types.ts @@ -0,0 +1,791 @@ +import type { OutputProperty, ToolResponse } from '@/tools/types' + +export interface BrexPaginationParams { + apiKey: string + cursor?: string + limit?: string +} + +export interface BrexMoney { + amount: number + currency: string | null +} + +export interface BrexExpenseReceipt { + id: string + download_uris?: string[] +} + +export interface BrexExpense { + id: string + memo: string | null + status: string | null + payment_status: string | null + expense_type: string | null + category: string | null + merchant_id: string | null + merchant: { raw_descriptor: string; mcc: string; country: string } | null + budget_id: string | null + budget: { id: string; name: string } | null + department_id: string | null + department: { id: string; name: string } | null + location_id: string | null + location: { id: string; name: string } | null + user_id: string | null + user: { id: string; first_name: string; last_name: string } | null + original_amount: BrexMoney | null + billing_amount: BrexMoney | null + purchased_amount: BrexMoney | null + usd_equivalent_amount: BrexMoney | null + purchased_at: string | null + updated_at: string + payment_posted_at: string | null + receipts: BrexExpenseReceipt[] + dashboard_url: string +} + +export interface BrexCardTransaction { + id: string + card_id: string | null + description: string + amount: BrexMoney + initiated_at_date: string + posted_at_date: string + type: string | null + merchant: { raw_descriptor: string; mcc: string; country: string } | null + expense_id: string | null +} + +export interface BrexCashTransaction { + id: string + description: string + amount: BrexMoney | null + initiated_at_date: string + posted_at_date: string + type: string | null + transfer_id: string | null +} + +export interface BrexCardAccount { + id: string + status: string | null + current_balance: BrexMoney | null + available_balance: BrexMoney | null + account_limit: BrexMoney | null + current_statement_period: { start_date: string; end_date: string } +} + +export interface BrexCashAccount { + id: string + name: string + status: string | null + current_balance: BrexMoney + available_balance: BrexMoney + account_number: string + routing_number: string + primary: boolean +} + +export interface BrexSpendLimitPeriodBalance { + start_date: string | null + end_date: string | null + start_time: string | null + end_time: string | null + amount_spent: BrexMoney | null + rollover_amount: BrexMoney | null +} + +export interface BrexUser { + id: string + first_name: string + last_name: string + email: string + status: string | null + manager_id: string | null + department_id: string | null + location_id: string | null + title_id: string | null +} + +export interface BrexDepartment { + id: string + name: string + description: string | null +} + +export interface BrexLocation { + id: string + name: string + description: string | null +} + +export interface BrexBudget { + budget_id: string + account_id: string + name: string + description: string | null + parent_budget_id: string | null + owner_user_ids: string[] + period_recurrence_type: string + start_date: string | null + end_date: string | null + amount: BrexMoney | null + spend_budget_status: string + limit_type: string | null +} + +export interface BrexSpendLimit { + id: string + account_id: string + name: string + description: string | null + parent_budget_id: string | null + status: string + period_recurrence_type: string + spend_type: string + owner_user_ids: string[] + member_user_ids: string[] + current_period_balance: BrexSpendLimitPeriodBalance | null +} + +export interface BrexVendor { + id: string + company_name: string | null + email: string | null + phone: string | null + payment_accounts: unknown[] +} + +export interface BrexTransfer { + id: string + counterparty: Record | null + description: string | null + payment_type: string + amount: BrexMoney + process_date: string | null + originating_account: Record + status: string + cancellation_reason: string | null + estimated_delivery_date: string | null + creator_user_id: string | null + created_at: string | null + display_name: string | null + external_memo: string | null +} + +export interface BrexCard { + id: string + owner: Record + status: string | null + last_four: string + card_name: string + card_type: string | null + limit_type: string + spend_controls: Record | null + billing_address: Record + expiration_date: Record + budget_id: string | null +} + +export interface BrexStatement { + id: string + start_balance: BrexMoney | null + end_balance: BrexMoney | null + period: { start_date: string; end_date: string } +} + +export interface BrexTitle { + id: string + name: string +} + +export interface BrexListExpensesParams extends BrexPaginationParams { + userIds?: string + statuses?: string + paymentStatuses?: string + purchasedAtStart?: string + purchasedAtEnd?: string +} + +export interface BrexGetExpenseParams { + apiKey: string + expenseId: string +} + +export interface BrexUpdateExpenseParams { + apiKey: string + expenseId: string + memo: string +} + +export interface BrexUploadReceiptParams { + apiKey: string + expenseId: string + file?: unknown + receiptName?: string +} + +export interface BrexMatchReceiptParams { + apiKey: string + file?: unknown + receiptName?: string +} + +export interface BrexListCardTransactionsParams extends BrexPaginationParams { + userIds?: string + postedAtStart?: string +} + +export interface BrexListCashTransactionsParams extends BrexPaginationParams { + accountId: string + postedAtStart?: string +} + +export interface BrexListUsersParams extends BrexPaginationParams { + email?: string +} + +export interface BrexGetUserParams { + apiKey: string + userId: string +} + +export interface BrexNameFilterParams extends BrexPaginationParams { + name?: string +} + +export interface BrexListSpendLimitsParams extends BrexPaginationParams { + memberUserIds?: string +} + +export interface BrexApiKeyParams { + apiKey: string +} + +export interface BrexGetCashAccountParams { + apiKey: string + accountId?: string +} + +export interface BrexListCardsParams extends BrexPaginationParams { + userId?: string +} + +export interface BrexListCashStatementsParams extends BrexPaginationParams { + accountId: string +} + +export interface BrexGetBudgetParams { + apiKey: string + budgetId: string +} + +export interface BrexGetSpendLimitParams { + apiKey: string + spendLimitId: string +} + +export interface BrexGetVendorParams { + apiKey: string + vendorId: string +} + +export interface BrexGetTransferParams { + apiKey: string + transferId: string +} + +export interface BrexListExpensesResponse extends ToolResponse { + output: { + items: BrexExpense[] + nextCursor: string | null + } +} + +export interface BrexGetExpenseResponse extends ToolResponse { + output: { + id: string + memo: string | null + status: string | null + paymentStatus: string | null + expenseType: string | null + category: string | null + merchantId: string | null + merchant: BrexExpense['merchant'] + budgetId: string | null + budget: BrexExpense['budget'] + departmentId: string | null + department: BrexExpense['department'] + locationId: string | null + location: BrexExpense['location'] + userId: string | null + user: BrexExpense['user'] + originalAmount: BrexMoney | null + billingAmount: BrexMoney | null + purchasedAmount: BrexMoney | null + usdEquivalentAmount: BrexMoney | null + purchasedAt: string | null + updatedAt: string + paymentPostedAt: string | null + receipts: BrexExpenseReceipt[] + dashboardUrl: string + } +} + +export interface BrexUpdateExpenseResponse extends ToolResponse { + output: { + id: string + memo: string | null + status: string | null + paymentStatus: string | null + category: string | null + merchantId: string | null + budgetId: string | null + originalAmount: BrexMoney | null + billingAmount: BrexMoney | null + purchasedAt: string | null + updatedAt: string + } +} + +export interface BrexUploadReceiptResponse extends ToolResponse { + output: { + receiptId: string + receiptName: string + expenseId: string | null + } +} + +export interface BrexListCardTransactionsResponse extends ToolResponse { + output: { + items: BrexCardTransaction[] + nextCursor: string | null + } +} + +export interface BrexListCashTransactionsResponse extends ToolResponse { + output: { + items: BrexCashTransaction[] + nextCursor: string | null + } +} + +export interface BrexListCardAccountsResponse extends ToolResponse { + output: { + accounts: BrexCardAccount[] + } +} + +export interface BrexListCashAccountsResponse extends ToolResponse { + output: { + items: BrexCashAccount[] + nextCursor: string | null + } +} + +export interface BrexListUsersResponse extends ToolResponse { + output: { + items: BrexUser[] + nextCursor: string | null + } +} + +export interface BrexGetUserResponse extends ToolResponse { + output: { + id: string + firstName: string + lastName: string + email: string + status: string | null + managerId: string | null + departmentId: string | null + locationId: string | null + titleId: string | null + } +} + +export interface BrexListDepartmentsResponse extends ToolResponse { + output: { + items: BrexDepartment[] + nextCursor: string | null + } +} + +export interface BrexListLocationsResponse extends ToolResponse { + output: { + items: BrexLocation[] + nextCursor: string | null + } +} + +export interface BrexListBudgetsResponse extends ToolResponse { + output: { + items: BrexBudget[] + nextCursor: string | null + } +} + +export interface BrexListSpendLimitsResponse extends ToolResponse { + output: { + items: BrexSpendLimit[] + nextCursor: string | null + } +} + +export interface BrexListVendorsResponse extends ToolResponse { + output: { + items: BrexVendor[] + nextCursor: string | null + } +} + +export interface BrexListTransfersResponse extends ToolResponse { + output: { + items: BrexTransfer[] + nextCursor: string | null + } +} + +export interface BrexGetCompanyResponse extends ToolResponse { + output: { + id: string + legalName: string + mailingAddress: Record | null + accountType: string | null + } +} + +export interface BrexListCardsResponse extends ToolResponse { + output: { + items: BrexCard[] + nextCursor: string | null + } +} + +export interface BrexListTitlesResponse extends ToolResponse { + output: { + items: BrexTitle[] + nextCursor: string | null + } +} + +export interface BrexGetCashAccountResponse extends ToolResponse { + output: { + id: string + name: string + status: string | null + currentBalance: BrexMoney + availableBalance: BrexMoney + accountNumber: string + routingNumber: string + primary: boolean + } +} + +export interface BrexListStatementsResponse extends ToolResponse { + output: { + items: BrexStatement[] + nextCursor: string | null + } +} + +export interface BrexGetBudgetResponse 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 BrexGetSpendLimitResponse 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 | null + } +} + +export interface BrexGetVendorResponse extends ToolResponse { + output: { + id: string + companyName: string | null + email: string | null + phone: string | null + paymentAccounts: unknown[] + } +} + +export interface BrexGetTransferResponse extends ToolResponse { + output: { + id: string + counterparty: Record | null + description: string | null + paymentType: string + amount: BrexMoney | null + processDate: string | null + originatingAccount: Record | null + status: string + cancellationReason: string | null + estimatedDeliveryDate: string | null + creatorUserId: string | null + createdAt: string | null + displayName: string | null + externalMemo: string | null + } +} + +export type BrexResponse = + | BrexListExpensesResponse + | BrexGetExpenseResponse + | BrexUpdateExpenseResponse + | BrexUploadReceiptResponse + | BrexListCardTransactionsResponse + | BrexListCashTransactionsResponse + | BrexListCardAccountsResponse + | BrexListCashAccountsResponse + | BrexListUsersResponse + | BrexGetUserResponse + | BrexListDepartmentsResponse + | BrexListLocationsResponse + | BrexListBudgetsResponse + | BrexListSpendLimitsResponse + | BrexListVendorsResponse + | BrexListTransfersResponse + | BrexGetCompanyResponse + | BrexListCardsResponse + | BrexListTitlesResponse + | BrexGetCashAccountResponse + | BrexListStatementsResponse + | BrexGetBudgetResponse + | BrexGetSpendLimitResponse + | BrexGetVendorResponse + | BrexGetTransferResponse + +export const BREX_MONEY_PROPERTIES: Record = { + amount: { + type: 'number', + description: 'Amount in the smallest unit of the currency (e.g., cents for USD)', + }, + currency: { + type: 'string', + description: 'ISO 4217 currency code (e.g., USD)', + optional: true, + }, +} + +export const BREX_SPEND_LIMIT_PERIOD_BALANCE_PROPERTIES: Record = { + start_date: { type: 'string', description: 'Start date of the current period', optional: true }, + end_date: { type: 'string', description: 'End date of the current period', optional: true }, + start_time: { + type: 'string', + description: 'Start time of the current period (ISO 8601)', + optional: true, + }, + end_time: { + type: 'string', + description: 'End time of the current period (ISO 8601)', + optional: true, + }, + amount_spent: { + type: 'json', + description: 'Amount spent in the current period', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + rollover_amount: { + type: 'json', + description: 'Amount rolled over from previous periods', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, +} + +export const BREX_EXPENSE_ITEM_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique expense ID' }, + memo: { type: 'string', description: 'Memo on the expense', optional: true }, + status: { + type: 'string', + description: + 'Expense status (DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED)', + optional: true, + }, + payment_status: { + type: 'string', + description: + 'Payment status (NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED)', + optional: true, + }, + expense_type: { + type: 'string', + description: 'Expense type (CARD, BILLPAY, REIMBURSEMENT, CLAWBACK, UNSET)', + optional: true, + }, + category: { + type: 'string', + description: + 'Expense category (e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES)', + optional: true, + }, + merchant: { + type: 'json', + description: 'Merchant details', + optional: true, + properties: { + raw_descriptor: { type: 'string', description: 'Raw merchant descriptor' }, + mcc: { type: 'string', description: 'Merchant category code' }, + country: { type: 'string', description: 'Merchant country' }, + }, + }, + user: { + type: 'json', + description: 'User who made the expense', + optional: true, + properties: { + id: { type: 'string', description: 'User ID' }, + first_name: { type: 'string', description: 'First name' }, + last_name: { type: 'string', description: 'Last name' }, + }, + }, + budget: { + type: 'json', + description: 'Budget the expense belongs to', + optional: true, + properties: { + id: { type: 'string', description: 'Budget ID' }, + name: { type: 'string', description: 'Budget name' }, + }, + }, + department: { + type: 'json', + description: 'Department of the expense owner', + optional: true, + properties: { + id: { type: 'string', description: 'Department ID' }, + name: { type: 'string', description: 'Department name' }, + }, + }, + location: { + type: 'json', + description: 'Location of the expense owner', + optional: true, + properties: { + id: { type: 'string', description: 'Location ID' }, + name: { type: 'string', description: 'Location name' }, + }, + }, + original_amount: { + type: 'json', + description: 'Original transaction amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + billing_amount: { + type: 'json', + description: 'Amount billed to the account', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + purchased_amount: { + type: 'json', + description: 'Amount at the time of purchase', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + receipts: { + type: 'array', + description: 'Receipts attached to the expense', + optional: true, + items: { + type: 'json', + properties: { + id: { type: 'string', description: 'Receipt ID' }, + download_uris: { type: 'array', description: 'Pre-signed receipt download URLs' }, + }, + }, + }, + purchased_at: { type: 'string', description: 'Purchase timestamp (ISO 8601)', optional: true }, + updated_at: { type: 'string', description: 'Last update timestamp (ISO 8601)' }, + dashboard_url: { type: 'string', description: 'Link to the expense in the Brex dashboard' }, +} + +export const BREX_CARD_TRANSACTION_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique transaction ID' }, + card_id: { type: 'string', description: 'ID of the card used', optional: true }, + description: { type: 'string', description: 'Transaction description' }, + amount: { + type: 'json', + description: 'Transaction amount', + properties: BREX_MONEY_PROPERTIES, + }, + initiated_at_date: { type: 'string', description: 'Date the transaction was initiated' }, + posted_at_date: { type: 'string', description: 'Date the transaction was posted' }, + type: { + type: 'string', + description: + 'Transaction type (PURCHASE, REFUND, CHARGEBACK, REWARDS_CREDIT, COLLECTION, BNPL_FEE)', + optional: true, + }, + merchant: { + type: 'json', + description: 'Merchant details', + optional: true, + properties: { + raw_descriptor: { type: 'string', description: 'Raw merchant descriptor' }, + mcc: { type: 'string', description: 'Merchant category code' }, + country: { type: 'string', description: 'Merchant country' }, + }, + }, + expense_id: { type: 'string', description: 'Associated expense ID', optional: true }, +} + +export const BREX_CASH_TRANSACTION_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique transaction ID' }, + description: { type: 'string', description: 'Transaction description' }, + amount: { + type: 'json', + description: 'Transaction amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + initiated_at_date: { type: 'string', description: 'Date the transaction was initiated' }, + posted_at_date: { type: 'string', description: 'Date the transaction was posted' }, + type: { type: 'string', description: 'Transaction type', optional: true }, + transfer_id: { type: 'string', description: 'Associated transfer ID', optional: true }, +} + +export const BREX_USER_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique user ID' }, + first_name: { type: 'string', description: 'First name' }, + last_name: { type: 'string', description: 'Last name' }, + email: { type: 'string', description: 'Email address' }, + status: { + type: 'string', + description: + 'User status (INVITED, ACTIVE, CLOSED, DISABLED, DELETED, PENDING_ACTIVATION, INACTIVE, ARCHIVED)', + optional: true, + }, + manager_id: { type: 'string', description: 'ID of the manager', optional: true }, + department_id: { type: 'string', description: 'Department ID', optional: true }, + location_id: { type: 'string', description: 'Location ID', optional: true }, + title_id: { type: 'string', description: 'Title ID', optional: true }, +} diff --git a/apps/sim/tools/brex/update_expense.ts b/apps/sim/tools/brex/update_expense.ts new file mode 100644 index 0000000000..bce7500167 --- /dev/null +++ b/apps/sim/tools/brex/update_expense.ts @@ -0,0 +1,100 @@ +import type { BrexUpdateExpenseParams, BrexUpdateExpenseResponse } 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 brexUpdateExpenseTool: ToolConfig = + { + id: 'brex_update_expense', + name: 'Brex Update Expense', + description: 'Update the memo of a Brex card expense', + 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)', + }, + expenseId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the card expense to update', + }, + memo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'New memo for the expense', + }, + }, + + request: { + url: (params) => + `${BREX_API_BASE}/v1/expenses/card/${encodeURIComponent(params.expenseId.trim())}`, + method: 'PUT', + headers: (params) => buildBrexHeaders(params.apiKey), + body: (params) => ({ memo: params.memo }), + }, + + transformResponse: async (response) => { + const data = await parseBrexJson(response) + return { + success: true, + output: { + id: data.id ?? '', + memo: data.memo ?? null, + status: data.status ?? null, + paymentStatus: data.payment_status ?? null, + category: data.category ?? null, + merchantId: data.merchant_id ?? null, + budgetId: data.budget_id ?? null, + originalAmount: data.original_amount ?? null, + billingAmount: data.billing_amount ?? null, + purchasedAt: data.purchased_at ?? null, + updatedAt: data.updated_at ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique expense ID' }, + memo: { type: 'string', description: 'Updated memo on the expense', optional: true }, + status: { + type: 'string', + description: + 'Expense status (DRAFT, SUBMITTED, APPROVED, OUT_OF_POLICY, VOID, CANCELED, SPLIT, SETTLED)', + optional: true, + }, + paymentStatus: { + type: 'string', + description: + 'Payment status (NOT_STARTED, PROCESSING, CANCELED, DECLINED, CLEARED, REFUNDING, REFUNDED, CASH_ADVANCE, CREDITED, AWAITING_PAYMENT, SCHEDULED)', + optional: true, + }, + category: { + type: 'string', + description: + 'Expense category (e.g., RESTAURANTS, RECURRING_SOFTWARE_AND_SAAS, AIRLINE_EXPENSES)', + optional: true, + }, + merchantId: { type: 'string', description: 'Merchant ID', optional: true }, + budgetId: { type: 'string', description: 'Budget ID', optional: true }, + originalAmount: { + type: 'json', + description: 'Original transaction amount', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + billingAmount: { + type: 'json', + description: 'Amount billed to the account', + optional: true, + properties: BREX_MONEY_PROPERTIES, + }, + purchasedAt: { type: 'string', description: 'Purchase timestamp (ISO 8601)', optional: true }, + updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' }, + }, + } diff --git a/apps/sim/tools/brex/upload_receipt.ts b/apps/sim/tools/brex/upload_receipt.ts new file mode 100644 index 0000000000..6196a4dc58 --- /dev/null +++ b/apps/sim/tools/brex/upload_receipt.ts @@ -0,0 +1,70 @@ +import type { BrexUploadReceiptParams, BrexUploadReceiptResponse } from '@/tools/brex/types' +import type { ToolConfig } from '@/tools/types' + +export const brexUploadReceiptTool: ToolConfig = + { + id: 'brex_upload_receipt', + name: 'Brex Upload Receipt', + description: 'Upload a receipt file and attach it to a specific Brex card expense', + 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)', + }, + expenseId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the card expense to attach the receipt to', + }, + file: { + type: 'file', + required: true, + visibility: 'user-or-llm', + description: 'Receipt file to upload (max 50 MB)', + }, + receiptName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Receipt file name including extension (defaults to the uploaded file name)', + }, + }, + + request: { + url: '/api/tools/brex/upload-receipt', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + apiKey: params.apiKey, + expenseId: params.expenseId, + file: params.file, + receiptName: params.receiptName, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + if (!data.success) { + throw new Error(data.error || 'Failed to upload receipt') + } + return { + success: true, + output: data.output, + } + }, + + outputs: { + receiptId: { type: 'string', description: 'Unique identifier of the receipt upload' }, + receiptName: { type: 'string', description: 'Name the receipt was uploaded with' }, + expenseId: { + type: 'string', + description: 'ID of the expense the receipt was attached to', + optional: true, + }, + }, + } diff --git a/apps/sim/tools/brex/utils.test.ts b/apps/sim/tools/brex/utils.test.ts new file mode 100644 index 0000000000..d7e7b727de --- /dev/null +++ b/apps/sim/tools/brex/utils.test.ts @@ -0,0 +1,53 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { appendBrexArrayParam, appendBrexPagination, toBrexDateTime } from '@/tools/brex/utils' + +describe('toBrexDateTime', () => { + it('strips a Z suffix by converting to naive UTC', () => { + expect(toBrexDateTime('2026-01-01T00:00:00Z')).toBe('2026-01-01T00:00:00') + expect(toBrexDateTime('2026-01-01T12:30:45.123Z')).toBe('2026-01-01T12:30:45') + }) + + it('converts timezone offsets to UTC before stripping', () => { + expect(toBrexDateTime('2026-01-01T02:00:00+02:00')).toBe('2026-01-01T00:00:00') + expect(toBrexDateTime('2025-12-31T19:00:00-05:00')).toBe('2026-01-01T00:00:00') + }) + + it('passes through timestamps without a timezone unchanged', () => { + expect(toBrexDateTime('2026-01-01T00:00:00')).toBe('2026-01-01T00:00:00') + expect(toBrexDateTime('2026-01-01T00:00:00.000')).toBe('2026-01-01T00:00:00.000') + }) + + it('passes through unparseable values unchanged', () => { + expect(toBrexDateTime('not-a-date-Z')).toBe('not-a-date-Z') + }) +}) + +describe('appendBrexArrayParam', () => { + it('appends repeated params from a comma-separated value, trimming entries', () => { + const query = new URLSearchParams() + appendBrexArrayParam(query, 'status[]', ' APPROVED, SETTLED ,, ') + expect(query.getAll('status[]')).toEqual(['APPROVED', 'SETTLED']) + }) + + it('does nothing for an empty value', () => { + const query = new URLSearchParams() + appendBrexArrayParam(query, 'status[]', undefined) + expect(query.toString()).toBe('') + }) +}) + +describe('appendBrexPagination', () => { + it('appends cursor and limit only when present', () => { + const query = new URLSearchParams() + appendBrexPagination(query, { cursor: 'abc', limit: '10' }) + expect(query.get('cursor')).toBe('abc') + expect(query.get('limit')).toBe('10') + + const empty = new URLSearchParams() + appendBrexPagination(empty, {}) + expect(empty.toString()).toBe('') + }) +}) diff --git a/apps/sim/tools/brex/utils.ts b/apps/sim/tools/brex/utils.ts new file mode 100644 index 0000000000..19edd9bb79 --- /dev/null +++ b/apps/sim/tools/brex/utils.ts @@ -0,0 +1,65 @@ +export const BREX_API_BASE = 'https://api.brex.com' + +/** + * Builds the standard headers for Brex API requests. + */ +export function buildBrexHeaders(apiKey: string): Record { + return { + Authorization: `Bearer ${apiKey}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + } +} + +/** + * Parses a Brex API response body, throwing a descriptive error for non-2xx responses. + */ +export async function parseBrexJson(response: Response) { + if (!response.ok) { + const text = await response.text() + let message = text + try { + const parsed = JSON.parse(text) + message = parsed.message ?? text + } catch { + message = text + } + throw new Error(`Brex API error (${response.status}): ${message}`) + } + return response.json() +} + +/** + * Appends a comma-separated value as repeated query parameters (Brex array syntax). + */ +export function appendBrexArrayParam(query: URLSearchParams, key: string, value?: string): void { + if (!value) return + for (const item of value.split(',')) { + const trimmed = item.trim() + if (trimmed) query.append(key, trimmed) + } +} + +/** + * Appends standard cursor/limit pagination parameters to a query. + */ +export function appendBrexPagination( + query: URLSearchParams, + params: { cursor?: string; limit?: string } +): void { + if (params.cursor) query.append('cursor', params.cursor) + if (params.limit) query.append('limit', params.limit) +} + +/** + * 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 + * timestamps on these endpoints, so offsets are converted to UTC and stripped. + */ +export function toBrexDateTime(value: string): string { + const trimmed = value.trim() + if (!/(?:z|[+-]\d{2}:?\d{2})$/i.test(trimmed)) return trimmed + const parsed = new Date(trimmed) + if (Number.isNaN(parsed.getTime())) return trimmed + return parsed.toISOString().slice(0, 19) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 2923847b02..91dcaf954f 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -312,6 +312,36 @@ import { boxSignResendRequestTool, } from '@/tools/box_sign' import { brandfetchGetBrandTool, brandfetchSearchTool } from '@/tools/brandfetch' +import { + brexGetBudgetTool, + brexGetCashAccountTool, + brexGetCompanyTool, + brexGetCurrentUserTool, + brexGetExpenseTool, + brexGetSpendLimitTool, + brexGetTransferTool, + brexGetUserTool, + brexGetVendorTool, + brexListBudgetsTool, + brexListCardAccountsTool, + brexListCardStatementsTool, + brexListCardsTool, + brexListCardTransactionsTool, + brexListCashAccountsTool, + brexListCashStatementsTool, + brexListCashTransactionsTool, + brexListDepartmentsTool, + brexListExpensesTool, + brexListLocationsTool, + brexListSpendLimitsTool, + brexListTitlesTool, + brexListTransfersTool, + brexListUsersTool, + brexListVendorsTool, + brexMatchReceiptTool, + brexUpdateExpenseTool, + brexUploadReceiptTool, +} from '@/tools/brex' import { brightDataCancelSnapshotTool, brightDataDiscoverTool, @@ -3669,6 +3699,34 @@ export const tools: Record = { athena_stop_query: athenaStopQueryTool, brandfetch_get_brand: brandfetchGetBrandTool, brandfetch_search: brandfetchSearchTool, + brex_get_budget: brexGetBudgetTool, + brex_get_cash_account: brexGetCashAccountTool, + brex_get_company: brexGetCompanyTool, + brex_get_current_user: brexGetCurrentUserTool, + brex_get_expense: brexGetExpenseTool, + brex_get_spend_limit: brexGetSpendLimitTool, + brex_get_transfer: brexGetTransferTool, + brex_get_user: brexGetUserTool, + brex_get_vendor: brexGetVendorTool, + brex_list_budgets: brexListBudgetsTool, + brex_list_card_accounts: brexListCardAccountsTool, + brex_list_card_statements: brexListCardStatementsTool, + brex_list_card_transactions: brexListCardTransactionsTool, + brex_list_cards: brexListCardsTool, + brex_list_cash_accounts: brexListCashAccountsTool, + brex_list_cash_statements: brexListCashStatementsTool, + brex_list_cash_transactions: brexListCashTransactionsTool, + brex_list_departments: brexListDepartmentsTool, + brex_list_expenses: brexListExpensesTool, + brex_list_locations: brexListLocationsTool, + brex_list_spend_limits: brexListSpendLimitsTool, + brex_list_titles: brexListTitlesTool, + brex_list_transfers: brexListTransfersTool, + brex_list_users: brexListUsersTool, + brex_list_vendors: brexListVendorsTool, + brex_match_receipt: brexMatchReceiptTool, + brex_update_expense: brexUpdateExpenseTool, + brex_upload_receipt: brexUploadReceiptTool, brightdata_cancel_snapshot: brightDataCancelSnapshotTool, brightdata_discover: brightDataDiscoverTool, brightdata_download_snapshot: brightDataDownloadSnapshotTool,