Skip to content

Create a Step-by-Step Developer Guide: MCP + Brex Integration

A complete developer tutorial for integrating the Brex API with accounting software via MCP. Covers OAuth2, transactions, pagination, QuickBooks/Xero mapping, rate limits, and troubleshooting.

Riya Sethi Riya Sethi · · 18 min read
Create a Step-by-Step Developer Guide: MCP + Brex Integration

To give an AI agent read and write access to Brex - pulling transactions, issuing virtual cards, adjusting budgets - you need more than a simple REST API wrapper. You must deploy a Model Context Protocol (MCP) server that translates the agent's natural language intent into standardized JSON-RPC tool calls, while managing OAuth tokens, rate limits, and Brex's specific schema quirks.

The timing for this architectural shift is undeniable. The AI-driven expense report automation market is projected to reach $5.44 billion by 2030, growing at a 14% CAGR. Capital One completed its $5.15 billion acquisition of Brex in April 2026, signaling a massive consolidation of corporate spend management and automated financial infrastructure.

If you are a product manager or engineering leader tasked with giving an LLM access to corporate financial data, you already know that brute-forcing point-to-point API integrations is an unscalable approach. Many engineering teams are actively trying to create a step-by-step developer guide: mcp + brex integration to standardize how their internal systems and customer-facing copilots interact with financial data.

This guide covers the architecture, the real pain points, and what it takes to make a Brex MCP server work securely and at scale in production. It also walks through the practical engineering of integrating Brex transactions with accounting systems like QuickBooks Online, Xero, and NetSuite - including code, field-level mapping, and error handling.

Why Integrate Brex with Accounting Software

Every Brex card swipe creates a raw transaction record. For that record to be useful to a finance team, it must be categorized, matched against a vendor, assigned to an expense account, and written into a general ledger as a balanced double-entry journal entry. Without this sync, finance teams are stuck exporting CSVs from Brex and manually keying data into QuickBooks or Xero - a process that does not scale past a dozen transactions per month.

The typical integration pattern looks like this:

  1. Read settled transactions from the Brex Transactions API.
  2. Map each transaction to an account in the destination system's Chart of Accounts, resolving the vendor/contact and applying tax rates.
  3. Write a normalized Expense or JournalEntry into the accounting system (QuickBooks Online, Xero, NetSuite, etc.).
  4. Listen for webhook events from Brex (like EXPENSE_PAYMENT_UPDATED) to trigger near-real-time syncs instead of polling.

The engineering complexity comes from the fact that every accounting system has its own schema, authentication, rate limits, and quirks. QuickBooks Online limits you to 500 requests per minute per company. Xero gives you 60 calls per minute and 5,000 per day. NetSuite requires orchestrating across REST, SuiteQL, and sometimes SOAP APIs. An MCP server that handles the Brex side is only half the problem.

Understanding the Brex API and MCP Architecture

Brex exposes a modern REST API at https://api.brex.com, but it comes with strict operational constraints. It supports two authentication methods: OAuth 2.0 for partner applications and user tokens for first-party account access. It enforces aggressive rate limits that return HTTP 429 Too Many Requests, and uses cursor-based pagination for listing resources like transactions and expenses.

Large Language Models (LLMs) cannot natively handle these operational constraints. An LLM does not know how to execute an OAuth refresh grant, nor does it inherently understand how to back off exponentially when it hits a rate limit.

The Model Context Protocol (MCP) bridges this gap. It acts as an intermediary layer. Instead of the LLM making raw HTTP requests to Brex, the LLM communicates with an MCP server using JSON-RPC 2.0. The MCP server exposes specific Brex endpoints as highly structured "tools" with strict JSON Schemas, handles the authentication layer, and normalizes the responses.

Here is how the request lifecycle operates in a production environment:

sequenceDiagram
    participant LLM as AI Agent
    participant MCP as MCP Server
    participant Truto as Truto Platform
    participant Brex as Brex API

    LLM->>MCP: tools/call (list_brex_transactions)
    MCP->>Truto: Proxy Request + MCP Token
    Truto->>Truto: Validate Token & Inject OAuth Bearer<br>Check Method Filters
    Truto->>Brex: GET /v2/transactions
    Brex-->>Truto: HTTP 200 (JSON)
    Truto-->>MCP: Normalized JSON-RPC Response
    MCP-->>LLM: Context delivered to Agent

To build this architecture yourself, you must solve several distinct engineering problems: token management, dynamic tool generation, rate limit handling, and - if you want to close the loop with accounting - ledger mapping and write operations.

Step 1: Authentication - Brex OAuth2 Flow and Token Management

Brex supports two authentication methods, and the choice determines your entire integration architecture.

User Tokens (First-Party Access)

If you are building an internal tool that accesses your own company's Brex data, you can generate a user token directly from the Brex dashboard:

  1. Sign in to dashboard.brex.com as an Account Admin or Card Admin.
  2. Navigate to Developer > Settings.
  3. Click Create Token and select the scopes your application needs (e.g., transactions.card.readonly).
  4. Copy and store the token securely - you will not see it again.

User tokens are passed as Bearer tokens in the Authorization header:

curl -X GET "https://api.brex.com/v2/transactions/card/primary?limit=10" \
  -H "Authorization: Bearer bxt_YOUR_TOKEN_HERE"

User tokens expire after 90 days of inactivity. If you are making regular API calls, they remain valid indefinitely. If a token is compromised, revoke it immediately from the Developer page.

OAuth 2.0 (Partner/Multi-Tenant Access)

If you are building a product that accesses other companies' Brex accounts, you need OAuth 2.0. This is the path that requires the most careful engineering.

The Brex OAuth flow uses the authorization code grant:

  • Authorization URL: https://accounts-api.brex.com/oauth2/default/v1/authorize
  • Token URL: https://accounts-api.brex.com/oauth2/default/v1/token

OAuth access tokens last one hour and then must be refreshed. This is the single biggest operational headache in a multi-tenant system. If you have 500 customer accounts, you have 500 tokens that need proactive refresh management.

Here is the token refresh flow in Python:

import requests
import time
 
def refresh_brex_token(client_id: str, client_secret: str, refresh_token: str) -> dict:
    """Refresh an expired Brex OAuth access token."""
    response = requests.post(
        "https://accounts-api.brex.com/oauth2/default/v1/token",
        data={
            "grant_type": "refresh_token",
            "client_id": client_id,
            "client_secret": client_secret,
            "refresh_token": refresh_token,
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    response.raise_for_status()
    token_data = response.json()
    # token_data contains: access_token, token_type, expires_in, refresh_token
    return token_data

Why Token Management Is the Hardest Part

In a multi-tenant SaaS environment, managing OAuth tokens is notoriously difficult. You must securely store the access token, monitor its expiration time, and execute a refresh grant before the access token expires.

If your MCP server attempts to call Brex with an expired token, the API will return an HTTP 401 Unauthorized. If your system fails to handle this gracefully, the AI agent will hallucinate a response or crash the workflow entirely.

When building a custom MCP server, engineers often hardcode token refresh logic into the request path. This creates massive latency spikes for the end-user whenever a token happens to expire right before their prompt.

Truto handles the entire OAuth lifecycle natively. Instead of waiting for a request to fail, the platform automatically refreshes Brex tokens shortly before they expire. If a refresh fails, Truto marks the account as needing re-authorization and fires a webhook (integrated_account:authentication_error) so your application can prompt the user. When the account successfully re-authenticates, a integrated_account:reactivated webhook fires automatically.

Info

Zero Data Retention
Truto's pass-through architecture ensures zero data retention, meaning sensitive Brex financial data is never stored in Truto's databases. The platform only stores the metadata and routing rules required to proxy the request. For more details on secure token management, read our guide on /blog/oauth-at-scale-the-architecture-of-reliable-token-refreshes/.

Step 2: Key Brex Endpoints for Accounting Integration

Before you can wire Brex data into an accounting system, you need to understand which Brex endpoints to call and what the response payloads look like.

Transactions API

The Transactions API is your primary data source. It returns settled transactions only - pending transactions are not available through the API.

Card transactions:

GET https://api.brex.com/v2/transactions/card/primary?limit=100
Authorization: Bearer <token>

Sample response:

{
  "next_cursor": "eyJsaW1pdCI6Miwib2Zmc2V0IjoyfQ==",
  "items": [
    {
      "id": "pste_ckuwiblvv1tn101l3zvscxis8",
      "card_id": "ncard_ckrz3n9b9013i01h5hgdyz7jh",
      "description": "Office Supplies Co.",
      "amount": {
        "amount": 12900,
        "currency": "USD"
      },
      "posted_at_date": "2026-01-15",
      "type": "PURCHASE"
    }
  ]
}

Key details to note:

  • Amounts are in cents. A value of 12900 means $129.00.
  • next_cursor is an opaque, base64-encoded string. Do not decode, parse, or modify it. Pass it back exactly as received.
  • limit defaults to 100 and maxes out at 1,000. Setting it above 1,000 returns a 400 Bad Request.

Cash transactions require a specific account ID:

GET https://api.brex.com/v2/transactions/cash/{cash_account_id}?limit=100

You can get the list of cash accounts from GET /v2/accounts/cash.

Webhooks API

Polling the Transactions API on a timer works, but it is wasteful and slow. For near-real-time syncs, subscribe to Brex webhook events.

Register a webhook subscription:

POST https://platform.brexapis.com/v1/webhooks
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: unique-key-here
 
{
  "url": "https://your-app.com/brex-webhook",
  "event_types": [
    "EXPENSE_PAYMENT_UPDATED",
    "TRANSFER_PROCESSED",
    "TRANSFER_FAILED"
  ]
}

The most relevant event types for accounting integration:

Event Type When It Fires
EXPENSE_PAYMENT_UPDATED A card charge settles, gets categorized, or has a receipt attached
TRANSFER_PROCESSED An outgoing payment (ACH, wire, check) completes
TRANSFER_FAILED An outgoing payment fails
Warning

Verify webhook signatures. Brex signs every webhook payload with HMAC-SHA256. Always validate the Webhook-Signature header against the signing secret from GET /v1/webhooks/secrets before processing. Use a constant-time comparison to prevent timing attacks.

Only one webhook endpoint can be registered per customer or client_id, but a single endpoint can listen to multiple event types.

Step 3: Exposing Brex Endpoints as MCP Tools

An MCP server must expose a /tools/list endpoint that returns an array of available operations. For Brex, this means converting endpoints like GET /v2/transactions and POST /v2/virtual_cards into JSON-RPC tools with explicit query_schema and body_schema definitions.

Manually writing and maintaining these JSON Schemas is a massive operational burden. Brex frequently updates its API schemas, and if your MCP tool schema drifts from the actual Brex API schema, the LLM will generate invalid requests.

Truto automatically generates MCP tools dynamically from Brex API documentation. The platform parses the OpenAPI specifications and documentation records, creating tools on the fly.

Tool names are generated as descriptive snake_case strings. For example, the Brex transaction endpoint becomes list_all_brex_transactions. The platform automatically injects specific instructions into the schema to guide the LLM.

Here is an example of how Truto dynamically structures the query schema for a Brex list tool:

{
  "name": "list_all_brex_transactions",
  "description": "Retrieve a list of all settled transactions for the account.",
  "query_schema": {
    "type": "object",
    "properties": {
      "limit": {
        "type": "string",
        "description": "The number of records to fetch"
      },
      "next_cursor": {
        "type": "string",
        "description": "The cursor to fetch the next set of records. Always send back exactly the cursor value you received without decoding, modifying, or parsing it."
      }
    }
  }
}

Notice the explicit instruction for next_cursor. LLMs have a tendency to URL-decode or modify base64-encoded cursor strings. By injecting strict natural language instructions directly into the JSON Schema description, Truto prevents the LLM from corrupting the pagination state.

For a deeper dive into this pattern, see our guide on /blog/auto-generated-mcp-tools-for-ai-agents-a-2026-architecture-guide/.

Step 4: Fetching and Paginating Brex Transactions (Code Walkthrough)

Here is a complete Python implementation that fetches all card transactions from Brex, handles pagination, and collects them into a single list.

import requests
from typing import Optional
 
BREX_BASE_URL = "https://api.brex.com"
 
def fetch_all_card_transactions(
    token: str,
    posted_at_start: Optional[str] = None,
    limit: int = 1000
) -> list[dict]:
    """Fetch all settled card transactions, handling cursor pagination."""
    all_transactions = []
    cursor = None
    headers = {"Authorization": f"Bearer {token}"}
 
    while True:
        params = {"limit": limit}
        if cursor:
            params["cursor"] = cursor
        if posted_at_start:
            params["posted_at_start"] = posted_at_start
 
        resp = requests.get(
            f"{BREX_BASE_URL}/v2/transactions/card/primary",
            headers=headers,
            params=params,
        )
 
        if resp.status_code == 429:
            # Rate limited - read Retry-After or back off
            retry_after = int(resp.headers.get("Retry-After", 5))
            print(f"Rate limited. Retrying in {retry_after}s...")
            import time
            time.sleep(retry_after)
            continue
 
        resp.raise_for_status()
        data = resp.json()
 
        all_transactions.extend(data.get("items", []))
 
        cursor = data.get("next_cursor")
        if not cursor:
            break
 
    return all_transactions

A few things to note about this implementation:

  • Set limit to 1,000 (the maximum). Fewer pages means fewer API calls, which matters when you are working within 1,000 requests per 60 seconds.
  • Never decode the cursor. Pass it back as-is. This is the single most common bug in Brex pagination implementations.
  • Handle 429 inline. The Retry-After header tells you exactly how long to wait.

Step 5: Mapping Brex Transactions to Accounting Ledgers

This is where the real complexity lives. A Brex transaction is a flat record - an amount, a merchant name, a date. An accounting system needs a balanced double-entry journal entry with a debit account, a credit account, a contact (vendor), a tax rate, and often a tracking category (department, class, or location).

Here is the field-level mapping from a Brex card transaction to a unified accounting schema:

Brex Transaction Field Accounting Target Notes
amount.amount Line item amount Divide by 100 (cents to dollars)
amount.currency Currency code USD, EUR, etc.
description Vendor/Contact name Fuzzy-match against existing contacts in the ledger
posted_at_date Transaction date ISO 8601 date
type Transaction classification PURCHASE = expense, COLLECTION = payment/refund
card_id Credit account Map to the credit card liability account in Chart of Accounts
(user-assigned) Debit account The expense category (e.g., "Meals & Entertainment", "Software")

Concrete Example: Brex Transaction to QuickBooks Journal Entry

Given this Brex transaction:

{
  "id": "pste_abc123",
  "description": "Amazon Web Services",
  "amount": { "amount": 49900, "currency": "USD" },
  "posted_at_date": "2026-06-01",
  "type": "PURCHASE"
}

The target journal entry in any accounting system needs two balanced lines:

{
  "transaction_date": "2026-06-01",
  "currency": "USD",
  "lines": [
    {
      "account": "Software & Cloud Services",
      "debit": 499.00,
      "credit": 0,
      "contact": "Amazon Web Services"
    },
    {
      "account": "Brex Credit Card (Liability)",
      "debit": 0,
      "credit": 499.00
    }
  ]
}

The debit side hits the expense account. The credit side hits the credit card liability account. That is standard double-entry bookkeeping.

The Schema Divergence Problem

The challenge is that each accounting system represents this differently:

  • QuickBooks Online uses a JournalEntry resource with a Line array where each line has a JournalEntryLineDetail containing PostingType (Debit or Credit) and an AccountRef.
  • Xero uses a ManualJournals endpoint with JournalLines where amounts are positive for debits and negative for credits, and contacts are referenced by ContactID.
  • NetSuite uses SuiteQL to query journal entries, with transactions split across transactionline and transactionaccountingline tables requiring multi-table JOINs.

Building and maintaining three separate mapping layers is where most integration projects go off the rails. Each system also has its own Chart of Accounts structure, vendor matching logic, and tax rate configuration.

Truto's Unified Accounting API handles this translation layer. You push a standardized expense or journal entry payload to a single endpoint, and the platform translates it into the provider-specific format for QuickBooks, Xero, NetSuite, or any other supported system. On the read side, you can dynamically fetch the destination system's Chart of Accounts, Vendors, Tax Rates, and Tracking Categories through the same unified schema to build your mapping interface.

Here is the transformation function that converts a Brex transaction into a standardized payload:

def brex_txn_to_journal_entry(
    txn: dict,
    expense_account_id: str,
    liability_account_id: str,
    contact_id: str
) -> dict:
    """Transform a Brex transaction into a unified journal entry payload."""
    amount_dollars = abs(txn["amount"]["amount"]) / 100
 
    return {
        "transaction_date": txn["posted_at_date"],
        "currency": txn["amount"]["currency"],
        "memo": f"Brex card: {txn['description']}",
        "lines": [
            {
                "account_id": expense_account_id,
                "contact_id": contact_id,
                "debit": amount_dollars,
                "credit": 0,
            },
            {
                "account_id": liability_account_id,
                "debit": 0,
                "credit": amount_dollars,
            },
        ],
    }

For a deeper look at how Truto normalizes the write path across QuickBooks, Xero, and NetSuite, see our guide on /blog/developer-tutorial-how-to-integrate-brex-with-xero-and-quickbooks/.

Step 6: Managing Brex API Rate Limits

Brex enforces strict rate limits to protect its infrastructure: 1,000 requests per 60 seconds per Client ID and Brex account. There are also daily caps - 1,000 transfers per 24 hours, 100 international wires per 24 hours, and 5,000 card creations per 24 hours.

When an AI agent attempts to pull thousands of transactions to perform an audit, it will inevitably hit these limits. Brex responds with an HTTP 429 Too Many Requests status code.

Handling this correctly is critical. If the MCP server absorbs the error and returns an empty array to the LLM, the AI agent will assume there are no more transactions and generate an inaccurate financial audit.

Retry with Exponential Backoff

Here is a reusable retry wrapper that respects rate limit headers:

import time
import random
 
def request_with_backoff(method, url, headers, params=None, max_retries=5):
    """Make an HTTP request with exponential backoff on 429 responses."""
    for attempt in range(max_retries):
        resp = requests.request(method, url, headers=headers, params=params)
 
        if resp.status_code != 429:
            return resp
 
        # Prefer Retry-After header if present
        retry_after = resp.headers.get("Retry-After")
        if retry_after:
            wait = int(retry_after)
        else:
            # Exponential backoff: 1s, 2s, 4s, 8s, 16s + jitter
            wait = (2 ** attempt) + random.uniform(0, 1)
 
        print(f"429 received. Waiting {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
        time.sleep(wait)
 
    raise Exception(f"Max retries exceeded for {url}")

The jitter (random component) is important. Without it, multiple clients that hit the limit simultaneously will all retry at the exact same time, creating a thundering herd that immediately triggers the limit again.

How Truto Handles Rate Limits

Truto normalizes upstream rate limit info into standardized IETF headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset).

It is important to note that Truto does not retry, throttle, or apply backoff on rate limit errors automatically. When the Brex API returns an HTTP 429, Truto passes that error directly to the caller.

This is a deliberate architectural decision. The caller (the AI agent or the orchestration framework like LangGraph) holds the context of the workflow. The caller is responsible for implementing retry and exponential backoff logic based on the normalized ratelimit-reset header. Masking rate limits at the API gateway layer leads to unpredictable LLM timeouts and zombie requests.

Tip

Reduce polling with webhooks. Brex's own rate limit documentation recommends using webhooks to reduce call volume. Subscribe to EXPENSE_PAYMENT_UPDATED events instead of polling /v2/transactions every few minutes. This frees up your rate limit budget for on-demand queries from the AI agent.

Read more about this architectural pattern in our guide on /blog/best-practices-for-handling-api-rate-limits-and-retries-across-multiple-third-party-apis/.

Step 7: Implementing Method Filtering for Security

Financial APIs carry immense risk. You likely do not want your AI agent to have the ability to arbitrarily issue new corporate cards or delete expense reports without human oversight.

When configuring an MCP server, you must restrict the toolset to the principle of least privilege.

Truto supports strict method filtering at the MCP token level. When creating an MCP server for Brex, you can configure the server to only expose read operations (get, list).

// Example: Creating a read-only Brex MCP server via the Truto API
POST /integrated-account/:id/mcp
{
  "name": "Brex Audit Agent MCP",
  "config": {
    "methods": ["read"] // Completely disables create, update, and delete tools
  }
}

The platform enforces this configuration at tool generation time. If the methods filter is set to ["read"], the create_a_brex_virtual_card tool is never generated, and the LLM never even knows the capability exists. This eliminates the risk of prompt injection leading to unauthorized financial mutations.

For an extra layer of security, Truto allows you to enable require_api_token_auth. By default, an MCP server's token URL is the only authentication required by the protocol. Enabling this flag forces the MCP client to also provide a valid Truto API token in the Authorization header, ensuring that possession of the MCP URL alone is insufficient to access the financial data.

Testing Your Brex Integration

Brex does not offer a public sandbox environment for API testing. The official documentation states: the only available API server is the production server at https://api.brex.com. This is a significant constraint compared to payment APIs like Stripe or Plaid, which provide dedicated test environments.

Here is how to work around this:

Use a Dedicated Test Brex Account

Create a separate Brex account (or a sub-account if your plan supports it) specifically for development. Generate a user token scoped to transactions.card.readonly and use it exclusively in your staging environment. This isolates test traffic from production data.

Mock the Brex API Locally

For unit and integration tests, mock the Brex API responses. The Brex API is described using OpenAPI, so you can use tools like Prism or WireMock to generate a local mock server from the spec:

# Using Prism to mock the Brex API from their OpenAPI spec
npx @stoplight/prism-cli mock https://developer.brex.com/openapi/transactions_api/

This gives you a local server that returns realistic responses matching the Brex schema, without hitting production.

Test Webhook Processing with Replay

Since you cannot trigger real Brex events on demand in a sandbox, capture a real webhook payload and replay it against your local endpoint. Use the sample verification data from Brex's docs to validate your signature verification logic independently of live events.

Test the Accounting Write Path Separately

QuickBooks Online, Xero, and NetSuite all provide sandbox environments. Test your ledger write logic against those sandboxes using hardcoded Brex transaction fixtures. This lets you validate the mapping and double-entry balancing without needing live Brex data.

Common Errors and Troubleshooting

Here are the most frequent errors you will encounter when integrating the Brex API with accounting systems, and how to fix them.

HTTP 401 Unauthorized

Cause: Expired or invalid token.

  • User tokens expire after 90 days of inactivity. If your cron job hasn't run in a while, the token may be dead.
  • OAuth tokens expire after 1 hour. If your refresh logic failed silently, subsequent calls will 401.

Fix: For user tokens, regenerate from the Brex dashboard. For OAuth, check your refresh token flow - ensure you are storing the new refresh token returned with each access token refresh (Brex may rotate refresh tokens).

HTTP 403 Forbidden

Cause: The token does not have the required scope for the endpoint.

Fix: Verify that your token includes the correct scope. For example, GET /v2/transactions/card/primary requires the transactions.card.readonly scope. Regenerate the token with the missing scope if needed.

HTTP 429 Too Many Requests

Cause: You have exceeded 1,000 requests in 60 seconds for this Client ID and Brex account.

Fix: Implement exponential backoff with jitter (see the code example in Step 6). Use limit=1000 on list calls to minimize total request count. Subscribe to webhooks for event-driven data instead of polling.

HTTP 400 Bad Request on Pagination

Cause: You set limit above 1,000, or you modified/decoded the next_cursor value.

Fix: Cap limit at 1,000. Pass the next_cursor value back to the API exactly as you received it - do not URL-decode, base64-decode, or trim it.

Unbalanced Journal Entries on Write

Cause: The total debits do not equal total credits in your journal entry payload.

Fix: Always calculate both sides from the same source amount. Watch for rounding errors when converting from cents to dollars. Use Decimal types instead of floating point for financial arithmetic.

from decimal import Decimal, ROUND_HALF_UP
 
def cents_to_dollars(cents: int) -> Decimal:
    return (Decimal(cents) / 100).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

Duplicate Transactions in the Ledger

Cause: Your sync ran twice and created duplicate journal entries.

Fix: Use the Brex transaction id as an idempotency key. Before writing to the accounting system, check whether a journal entry with that external reference already exists. Brex also supports Idempotency-Key headers on POST requests - use them for every write operation.

The Build vs. Buy Decision for Brex MCP Servers

When evaluating how to connect your AI agents to Brex, you face a saturated market of integration approaches. Understanding the architectural tradeoffs of each is essential for long-term stability.

Zapier (Zapier MCP Server)

Zapier positions itself as a no-code way to connect AI agents to Brex via its existing action library. Zapier's MCP server connects AI tools to Zapier's 30,000+ actions.

The problem with this approach is the heavy middleman orchestration layer. The AI agent interacts with Zapier's abstraction, not the native Brex API. This introduces significant latency, makes debugging nearly impossible when a request fails, and forces your financial data through a third-party orchestration engine that may not meet your compliance requirements.

Rootly and Custom Open-Source Servers

Rootly built an open-source MCP server specifically for incident management. Brex engineers themselves report that integrating Rootly directly into editors via MCP accelerates incident investigation and increases developer efficiency. This proves the enterprise value of MCP.

However, relying on community-built open-source servers (like mcp-brex-server) requires your engineering team to self-host the infrastructure, manually handle the OAuth lifecycle, and constantly maintain the JSON Schemas whenever Brex updates its API.

Make.com

Make.com positions itself as a visual workflow builder for Brex. While highly effective for internal, static automation rules (e.g., "If an expense is over $500, send a Slack message"), it lacks the native AI agent tool-calling capabilities of an MCP server. You cannot easily expose Make.com workflows to an LLM as dynamic, self-describing tools.

Declarative Unified APIs (Truto)

Using a declarative unified API platform like Truto provides the direct API access of a custom build with the zero-maintenance benefits of a managed service.

Truto automatically generates the MCP tools, handles the OAuth lifecycle, normalizes the rate limit headers, and provides strict method filtering - all without storing your customer's financial data. On the accounting side, the Unified Accounting API lets you read and write to QuickBooks, Xero, NetSuite, and other ledgers through a single schema - so your integration covers both the Brex read path and the accounting write path. It allows your engineers to focus on the AI agent's prompt engineering and workflow logic rather than building API glue code.

Accelerating Financial AI Integrations

The convergence of AI agents and corporate spend management is fundamentally changing how businesses operate. As the market expands toward $5.44 billion by 2030, the companies that win will be those that can securely and reliably connect their LLMs to financial systems of record.

Building a custom Brex integration from scratch requires months of engineering effort dedicated to OAuth state management, schema generation, and rate limit handling. When you add the accounting write path - mapping transactions to balanced journal entries across QuickBooks, Xero, and NetSuite - the engineering surface area doubles. By leveraging a managed MCP infrastructure with a unified accounting layer, you bypass the boilerplate and deliver intelligent financial automation to your customers immediately.

FAQ

Does the Brex API provide a sandbox or test environment?
No. Brex only offers a production API server at https://api.brex.com. To test safely, use a dedicated test Brex account, mock the API locally using Brex's OpenAPI spec with tools like Prism, and test your accounting write path against QuickBooks/Xero/NetSuite sandbox environments separately.
What are the Brex API rate limits?
Brex allows up to 1,000 requests per 60 seconds per Client ID and Brex account. There are also daily caps: 1,000 transfers, 100 international wires, and 5,000 card creations per 24 hours. Exceeding these returns HTTP 429. Implement exponential backoff with jitter and use webhooks to reduce polling volume.
How long do Brex OAuth tokens last?
Brex OAuth access tokens expire after one hour and must be refreshed using the refresh token. User tokens (generated from the dashboard) expire after 90 days of inactivity but remain valid indefinitely with regular use.
How do I map a Brex transaction to a QuickBooks or Xero journal entry?
Convert the Brex amount from cents to dollars, match the merchant description to a vendor/contact in the accounting system, assign a debit to the appropriate expense account and a credit to the credit card liability account. Each accounting system uses a different schema for this: QuickBooks uses JournalEntryLineDetail, Xero uses ManualJournals, and NetSuite uses SuiteQL with transactionline JOINs.
Does the Brex API return real-time transaction data?
No. Brex only returns settled transactions through the Transactions API. Pending transactions are not available. Subscribe to the EXPENSE_PAYMENT_UPDATED webhook event for near-real-time notifications when card charges settle.

More from our Blog