How to Build Accounting Integrations with Claude Code

Learn how to build accounting integrations with Claude Code using the QuickBooks MCP server, Apideck MCP server, and Apideck unified API. Includes working code examples for OAuth, invoice creation, and multi-platform support.

Kateryna PoryvayKateryna Poryvay

Kateryna Poryvay

9 min read
How to Build Accounting Integrations with Claude Code

Most accounting integrations start the same way. A developer opens the QuickBooks API docs, reads through the OAuth 2.0 flow, finds out there are seventeen different account types, realizes that Xero uses a completely different data model, and spends the next two weeks writing plumbing that will need to be rewritten every time a new accounting platform gets added to the roadmap.

Claude Code changes how this work gets done. Not because it writes boilerplate faster (though it does), but because the combination of agentic coding, MCP tool access, and unified APIs removes entire categories of the problem. This post walks through three ways to build accounting integrations: calling platform APIs directly, connecting via MCP, and using Apideck's unified accounting API to abstract the platform layer entirely.

Direct API: QuickBooks as a starting point

The direct approach is the right choice when you are building against a single accounting platform and want full control over the data model. Claude Code handles the OAuth dance, the pagination logic, and the error handling without you having to think through each step.

Here is what a working Claude Code session looks like when you want to create an invoice in QuickBooks Online:

claude "Write a Node.js function that authenticates with QuickBooks Online using OAuth 2.0 and creates an invoice for a customer. Use the intuit-oauth and node-quickbooks packages. The invoice should accept customer ID, line items with amount and description, and a due date. Handle token refresh automatically."

Claude Code will produce something close to this:

const OAuthClient = require('intuit-oauth');
const QuickBooks = require('node-quickbooks');

const oauthClient = new OAuthClient({
  clientId: process.env.QBO_CLIENT_ID,
  clientSecret: process.env.QBO_CLIENT_SECRET,
  environment: 'production',
  redirectUri: process.env.QBO_REDIRECT_URI,
});

async function createInvoice({ customerId, lineItems, dueDate, realmId, token }) {
  if (oauthClient.isAccessTokenValid() === false) {
    const authResponse = await oauthClient.refreshUsingToken(token.refresh_token);
    token = authResponse.getJson();
  }

  const qbo = new QuickBooks(
    process.env.QBO_CLIENT_ID,
    process.env.QBO_CLIENT_SECRET,
    token.access_token,
    false,
    realmId,
    true,
    false,
    null,
    '2.0',
    token.refresh_token
  );

  const invoice = {
    CustomerRef: { value: customerId },
    DueDate: dueDate,
    Line: lineItems.map((item, index) => ({
      Id: String(index + 1),
      LineNum: index + 1,
      Amount: item.amount,
      DetailType: 'SalesItemLineDetail',
      SalesItemLineDetail: {
        ItemRef: { value: '1', name: 'Services' },
        UnitPrice: item.amount,
        Qty: 1,
        TaxCodeRef: { value: 'NON' },
      },
      Description: item.description,
    })),
  };

  return new Promise((resolve, reject) => {
    qbo.createInvoice(invoice, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

This works. But now you need the same functionality for Xero. Xero's invoice creation endpoint uses Invoices (plural), the line item structure is different, and the authentication token exchange follows a different PKCE flow. Claude Code can write that version too, but now you have two codepaths to maintain and two different webhook parsers for the same business event.

That maintenance cost compounds. If you add Sage, FreshBooks, or NetSuite, you are not just adding connectors. You are adding three separate mental models for what an "invoice" or a "bill" means, and the divergence grows with every platform.

Via QuickBooks' own MCP server

If your integration only needs to cover QuickBooks Online, Intuit's own MCP server is the most direct path. They published intuit/quickbooks-online-mcp-server on GitHub, which covers 143 tools across 29 entity types with full CRUD operations, plus 11 financial reports including Balance Sheet, P&L, and Cash Flow.

Clone and build it first:

git clone https://github.com/intuit/quickbooks-online-mcp-server.git
cd quickbooks-online-mcp-server
npm install && npm run build

Then register it in Claude Code via .claude.json:

{
  "mcpServers": {
    "quickbooks": {
      "command": "node",
      "args": ["path/to/quickbooks-online-mcp-server/dist/index.js"],
      "env": {
        "QUICKBOOKS_CLIENT_ID": "your_client_id",
        "QUICKBOOKS_CLIENT_SECRET": "your_client_secret",
        "QUICKBOOKS_REFRESH_TOKEN": "your_refresh_token",
        "QUICKBOOKS_REALM_ID": "your_realm_id",
        "QUICKBOOKS_ENVIRONMENT": "sandbox"
      }
    }
  }
}

A note on auth. The QUICKBOOKS_REFRESH_TOKEN value is not something the MCP server generates for you. You get it by completing the OAuth 2.0 authorization flow once, either through Intuit's developer portal or your own callback endpoint. QBO access tokens expire after one hour; the server handles rotation automatically from there. What you are responsible for is storing the latest refresh token after each rotation, since QBO invalidates the previous one on every exchange. For personal dev tooling, the .env approach above is fine. For a product where each of your customers connects their own QBO account, you need a backend that captures per-user refresh tokens, stores them, and injects the right one at session time rather than using a shared env var.

Once registered, Claude Code has live access to your QBO company data:

claude "Using the QuickBooks MCP, pull all open invoices, identify customers with more than $10,000 outstanding, and generate a collections priority report in markdown."

Or inside a development session:

claude "Read the chart of accounts from the QuickBooks MCP and scaffold a TypeScript module that maps each account to our internal ledger category enum. Write tests for the mapping logic."

The tradeoff is that this server runs as a local stdio process, tied to a single QBO company per session. For a product where users connect different accounting platforms, you need a different approach.

Via Apideck: one integration, every accounting platform

The unified accounting API model is the default for companies adding accounting integrations as a product feature. You write the integration once against Apideck's normalized schema, and your users can connect QuickBooks, Xero, Sage, NetSuite, FreshBooks, or any of the 26 supported platforms without you touching the integration layer.

Creating an invoice across any connected accounting platform looks like this:

const Apideck = require('@apideck/node');

const apideck = new Apideck({
  apiKey: process.env.APIDECK_API_KEY,
  appId: process.env.APIDECK_APP_ID,
  consumerId: req.user.consumerId,
});

async function createInvoice({ lineItems, customerId, dueDate }) {
  const response = await apideck.accounting.invoicesAdd({
    invoice: {
      type: 'accounts_receivable',
      customer: { id: customerId },
      due_date: dueDate,
      line_items: lineItems.map(item => ({
        description: item.description,
        unit_price: item.amount,
        quantity: 1,
        total_amount: item.amount,
      })),
      currency: 'USD',
    },
  });

  return response.data;
}

The same function works whether the end user has connected QuickBooks, Xero, or Sage Business Cloud. Apideck normalizes the data model on the way in and maps the response back to the same schema on the way out.

Claude Code accelerates this further. Instead of reading through the Apideck API reference and writing the integration from scratch, you can give Claude Code a task:

claude "Using the Apideck Node SDK, build a sync module that pulls chart of accounts from the connected accounting platform, maps each account to our internal category taxonomy stored in accounts-taxonomy.json, and writes the mapping to Postgres. Handle pagination. Log any accounts that don't match a category."

Claude Code reads accounts-taxonomy.json, inspects the SDK types, writes the sync logic, and generates the Postgres schema. You can combine both the MCP and the unified API approaches in the same session: use the MCP server to pull live sample data and understand the shape of what you are working with, then use Claude Code to build and test the REST integration against that data.

claude "Using the Apideck MCP, pull a sample of 20 invoices from the test QBO connection. Then write a TypeScript function that replicates that invoice creation via the Apideck REST API. Test against the sandbox environment."

Which approach fits which problem

The cleanest mental model: use MCP when the agent decides what to call and when; use the REST API when your own application code is making those decisions. An AI bookkeeper that reads invoices and flags anomalies belongs on MCP. A scheduled sync job that runs every hour belongs on the REST API.

ApproachBest forLimitation
Direct APISingle-platform builds, platform-specific fields, white-labeled products with a fixed accounting systemOne codebase per platform; maintenance cost grows with every new connector added
QuickBooks MCP serverAgentic workflows scoped to QBO: AI agents that read and write QBO data on behalf of users, plus dev-time exploration and testingTied to a single QBO company per process; no multi-platform support
Apideck MCP serverAgentic workflows that need to reach across multiple accounting platforms; AI agents where the platform is determined at runtime by the connected accountNot the right layer for deterministic programmatic integrations where your application controls the logic
Apideck unified APIProgrammatic integrations where your application drives the logic: syncing, webhooks, scheduled jobs, multi-tenant products shipping accounting features to end users at scaleNormalized schema means some platform-specific fields are not exposed

Claude Code makes all four paths faster. For most product teams, the practical setup is: Apideck MCP for exploration and agent-driven workflows, Apideck REST API for production programmatic integrations, and Claude Code doing the implementation across both.

Ready to get started?

Scale your integration strategy and deliver the integrations your customers need in record time.

Ready to get started?
Talk to an expert

Trusted by fast-moving product & engineering teams

JobNimbus
Blue Zinc
Exact
Drata
Octa
Apideck Blog

Insights, guides, and updates from Apideck

Discover company news, API insights, and expert blog posts. Explore practical integration guides and tech articles to make the most of Apideck's platform.