<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Apideck</title>
    <description>The latest articles on DEV Community by Apideck (apideck).</description>
    <link>https://dev.to/apideck</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F10330%2F83f9e7ec-19ff-4cba-9ea1-8d42e6e4dfb3.png</url>
      <title>DEV Community: Apideck</title>
      <link>https://dev.to/apideck</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/apideck"/>
    <language>en</language>
    <item>
      <title>Xero Scopes: What Changed and What It Means for Your Integration</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 24 Jul 2026 09:29:40 +0000</pubDate>
      <link>https://dev.to/apideck/xero-scopes-what-changed-and-what-it-means-for-your-integration-36ia</link>
      <guid>https://dev.to/apideck/xero-scopes-what-changed-and-what-it-means-for-your-integration-36ia</guid>
      <description>&lt;p&gt;Xero has replaced two broad OAuth 2.0 scopes with a set of fine-grained scopes that limit each app to only the specific endpoints it needs. If you created your Xero app on or after March 2, 2026, the old broad scopes are simply not available to you. If your app predates that cutoff, you have until September 2027 to migrate.&lt;/p&gt;

&lt;p&gt;This is part of a larger restructuring of the Xero developer platform that also includes &lt;a href="https://dev.to/blog/xero-api-pricing-and-the-app-partner-program"&gt;new API pricing tiers&lt;/a&gt; and premium-gating of the Journals endpoint. The scope changes themselves are a genuine security improvement. But combined with the pricing changes, they create real planning work for any team building on Xero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly changed
&lt;/h2&gt;

&lt;p&gt;Two broad scopes are being deprecated: &lt;code&gt;accounting.transactions&lt;/code&gt; and &lt;code&gt;accounting.reports.read&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before and after: transaction scopes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before (deprecated)&lt;/th&gt;
&lt;th&gt;After (granular)&lt;/th&gt;
&lt;th&gt;Endpoints covered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.transactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.invoices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credit notes, invoices, linked transactions, purchase orders, quotes, repeating invoices, items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.transactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.payments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Batch payments, overpayments, payments, prepayments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.transactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.banktransactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bank transactions, bank transfers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.transactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.manualjournals&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual journals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.transactions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.classicexpenses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expense claims, receipts (deprecated endpoint)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each granular scope also has a &lt;code&gt;.read&lt;/code&gt; variant for read-only access. So if your app only needs to pull invoice data without writing, you request &lt;code&gt;accounting.invoices.read&lt;/code&gt; instead of the full scope.&lt;/p&gt;

&lt;p&gt;One detail worth noting: the items endpoint is available via both the &lt;code&gt;accounting.invoices&lt;/code&gt; and &lt;code&gt;accounting.settings&lt;/code&gt; scopes. If your app only works with items and not invoices, you do not need the invoices scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before and after: report scopes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before (deprecated)&lt;/th&gt;
&lt;th&gt;After (granular)&lt;/th&gt;
&lt;th&gt;Reports covered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.aged.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aged Payables by Contact, Aged Receivables by Contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.balancesheet.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Balance Sheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.banksummary.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bank Summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.executivesummary.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executive Summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.profitandloss.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Profit and Loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.trialbalance.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trial Balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accounting.reports.taxreports.read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GST and BAS reports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Scopes that are not changing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;accounting.contacts&lt;/code&gt;, &lt;code&gt;accounting.settings&lt;/code&gt;, &lt;code&gt;accounting.attachments&lt;/code&gt;, and &lt;code&gt;accounting.budgets&lt;/code&gt; remain available to every app regardless of when it was created. Non-Accounting API scopes like Payroll, Files, Assets, Projects, and the Finance API are also unaffected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-track timeline
&lt;/h2&gt;

&lt;p&gt;This is the most important detail for development teams to understand.&lt;/p&gt;

&lt;p&gt;Apps created before March 2, 2026 can continue using the broad &lt;code&gt;accounting.transactions&lt;/code&gt; and &lt;code&gt;accounting.reports.read&lt;/code&gt; scopes until September 2027. During the transition, these apps can use a mix of broad and granular scopes simultaneously. Scopes are additive, so a user who re-authorizes receives both the legacy and new scope grants on their token.&lt;/p&gt;

&lt;p&gt;By end of April 2026, Xero will assign the new granular scopes to all existing apps in the developer portal. But these will not automatically propagate to existing tokens. You need to update your authorization URL to request the granular scopes, and each connected user must re-authorize to receive the new permissions. There is no silent migration path. Explicit user consent is required for every connection.&lt;/p&gt;

&lt;p&gt;Apps created on or after March 2, 2026 have no access to the broad scopes at all. Their authorization links must specify only granular scopes from day one. If a new app calls an endpoint without the matching granular scope, the API returns HTTP 401 Forbidden with an &lt;code&gt;insufficient_scope&lt;/code&gt; error. If you are &lt;a href="https://dev.to/blog/how-to-get-xero-api-key"&gt;setting up a Xero app for the first time&lt;/a&gt;, you will only see the new granular scopes in the developer portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Journals, manual journals, and bank feeds: three different things
&lt;/h2&gt;

&lt;p&gt;This is where the changes get most confusing, because three closely related concepts are governed by entirely different access regimes.&lt;/p&gt;

&lt;p&gt;Manual journals (the &lt;code&gt;/api.xro/2.0/ManualJournals&lt;/code&gt; endpoint) remain available at all pricing tiers under the new &lt;code&gt;accounting.manualjournals&lt;/code&gt; granular scope. This endpoint handles user-created journal entries and is not restricted beyond requiring the correct scope.&lt;/p&gt;

&lt;p&gt;The Journals endpoint (&lt;code&gt;/api.xro/2.0/Journals&lt;/code&gt;) returns system-generated double-entry journal lines for every transaction in the ledger. This endpoint is now a premium feature gated behind the Advanced pricing tier, which starts at $1,445 AUD per month. Access requires both a security assessment and explicit use-case approval from Xero. If your integration depends on full ledger reconciliation or audit trail functionality, this is the endpoint that matters, and it now comes with a significant cost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;Bank feeds&lt;/a&gt; operate under their own separate API and scope (&lt;code&gt;bankfeeds&lt;/code&gt;), which is not affected by the accounting scope changes at all. The Bank Feeds API has its own certification requirement and separate contractual terms. If your app only pushes bank statement data into Xero, the &lt;code&gt;accounting.transactions&lt;/code&gt; deprecation does not impact you.&lt;/p&gt;

&lt;p&gt;Bank transactions, meaning the read and write endpoints for reconciled bank line items within Xero's ledger, move from the old broad scope to &lt;code&gt;accounting.banktransactions&lt;/code&gt;. This is distinct from bank feeds. Bank feeds push raw statement data in. Bank transactions are the resulting accounting records inside Xero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need to do
&lt;/h2&gt;

&lt;p&gt;If you have an existing Xero integration, the migration path follows a few steps.&lt;/p&gt;

&lt;p&gt;Update your authorization URL to replace the broad scopes with the specific granular scopes your app actually needs. Only request the scopes you use. Requesting everything defeats the purpose and will make users hesitant to authorize.&lt;/p&gt;

&lt;p&gt;Let new users authorize with the new scopes naturally. For existing connected users, let re-authorization happen organically when users add new organizations or refresh their connection. The additive scope behavior means users accumulate both scope sets during the transition.&lt;/p&gt;

&lt;p&gt;Before September 2027, migrate any remaining connections that still only have broad scopes. For holdouts, you can force re-authorization by removing their connection via the API, which requires them to reconnect with the new scopes.&lt;/p&gt;

&lt;p&gt;For testing, create a new app in the Xero developer portal. It will default to granular scopes and can be tested against a Demo Company. No SDK changes are needed since the scopes operate at the OAuth authorization layer.&lt;/p&gt;

&lt;p&gt;If you are building a new integration from scratch, you start with granular scopes by default. Plan your scope requests carefully based on which endpoints you actually need.&lt;/p&gt;

&lt;p&gt;If you are using Apideck to connect to Xero, you can configure which scopes your app requests in the Apideck dashboard. Our &lt;a href="https://developers.apideck.com/connectors/xero/docs/application_owner+oauth_credentials" rel="noopener noreferrer"&gt;Xero connector setup guide&lt;/a&gt; walks through entering your OAuth credentials and reviewing the scopes granted in Xero against the scopes you are requesting in Apideck.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;The granular scope change fulfills a long-standing feature request on Xero's UserVoice forum, where developers have been asking for finer-grained permissions since at least 2010. For years, requesting invoice access also exposed bank balances and salary data because there was only one broad scope covering all of it. Fine-grained permissions are a real improvement.&lt;/p&gt;

&lt;p&gt;But the timing is not great. Xero bundled this change with connection-based pricing tiers and premium-gating of key endpoints like Journals. For developers who have been building on the platform for years with free API access, the combined effect feels like a significant shift in the economics of building on Xero.&lt;/p&gt;

&lt;p&gt;This is part of a broader pattern across accounting platforms. As we covered in our &lt;a href="https://www.apideck.com/blog/accounting-and-erp-partnership-guide" rel="noopener noreferrer"&gt;overview of accounting API pricing and partnerships&lt;/a&gt;, every major provider is evolving its developer program in a direction that adds cost and complexity. QuickBooks has its own partnership requirements. Sage remains free but has a smaller market footprint. NetSuite charges separately through its SuiteCloud licensing.&lt;/p&gt;

&lt;p&gt;For teams building &lt;a href="https://dev.to/blog/accounting-integration"&gt;accounting integrations&lt;/a&gt; that need to support multiple platforms, the platform-specific scope and pricing changes reinforce the case for abstracting away provider differences. Whether you build direct integrations or use a unified API layer, the goal is the same: insulate your product from the policy changes of any single platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key resources
&lt;/h2&gt;

&lt;p&gt;Xero's developer blog post on the scope changes: &lt;a href="https://devblog.xero.com/upcoming-changes-to-xero-accounting-api-scopes-705c5a9621a0" rel="noopener noreferrer"&gt;Upcoming changes to Xero Accounting API Scopes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xero's full scope reference documentation: &lt;a href="https://developer.xero.com/documentation/guides/oauth2/scopes/" rel="noopener noreferrer"&gt;OAuth 2.0 Scopes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a detailed breakdown of the pricing tiers that accompany these scope changes: &lt;a href="https://www.apideck.com/blog/xero-api-pricing-and-the-app-partner-program" rel="noopener noreferrer"&gt;Xero API Pricing and the App Partner Program&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For teams evaluating alternatives alongside Xero: &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate with in 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a broader look at the Xero integration landscape: &lt;a href="https://www.apideck.com/blog/xero-integrations" rel="noopener noreferrer"&gt;Xero Integrations: The Complete Guide for 2026&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>unifiedapi</category>
      <category>xero</category>
    </item>
    <item>
      <title>What Is ERP Banking? The Shift From Portal to Platform</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 20 Jul 2026 20:40:53 +0000</pubDate>
      <link>https://dev.to/apideck/what-is-erp-banking-the-shift-from-portal-to-platform-c85</link>
      <guid>https://dev.to/apideck/what-is-erp-banking-the-shift-from-portal-to-platform-c85</guid>
      <description>&lt;p&gt;Every morning, in corporate finance offices around the world, a treasurer logs into one system to check bank balances, switches to another to initiate payments, then copies data into a spreadsheet to reconcile the general ledger. Nobody designed this workflow. It emerged from decades of banks and accounting platforms evolving independently, and finance teams filling the gaps with manual effort.&lt;/p&gt;

&lt;p&gt;ERP banking is the fix. The idea is simple: instead of making business customers toggle between a bank portal and their accounting software, you embed the banking services directly inside the ERP or accounting system. Payments, balances, reconciliation, cash reporting. All from the same environment where the rest of the financial work happens.&lt;/p&gt;

&lt;p&gt;The term comes from analyst firm Datos Insights. Their definition: ERP banking is a model where corporate clients access and execute banking capabilities directly from their enterprise systems. The bank becomes invisible infrastructure. The customer never leaves their system of record.&lt;/p&gt;

&lt;h2&gt;
  
  
  ERP banking is not ERP for banks
&lt;/h2&gt;

&lt;p&gt;There is a naming collision worth clearing up.&lt;/p&gt;

&lt;p&gt;Search for "ERP banking" and most results describe ERP software used by banks for their own internal operations. SAP for a bank's back office. Oracle for a bank's financial reporting. That is a different topic entirely.&lt;/p&gt;

&lt;p&gt;ERP banking as Datos Insights defines it, and as the commercial banking industry uses the term, describes the opposite direction. Banking services flow into the customer's ERP. The bank is the service provider. The ERP is the destination. One is a procurement decision for the bank's IT department. The other is a product strategy for the bank's commercial banking division.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is happening now
&lt;/h2&gt;

&lt;p&gt;Corporate treasurers are already voting with their feet. Datos Insights surveyed over 1,000 corporate users across 11 countries and found that integration with internal ERP systems is the top priority for corporate treasurers. More than one in four said they would likely switch their primary bank within two years over better technology integration from a competitor. A Nine Wave survey put it more starkly: 85% of CFOs would switch banks for direct ERP-to-bank connectivity. These are not hypothetical preferences. They are already driving churn.&lt;/p&gt;

&lt;p&gt;The same Datos Insights research found that 58% of businesses already use a fintech for a core cash management or treasury service. Every payment that runs through a fintech instead of a bank is revenue the bank loses. ERP banking is how banks respond: by meeting the customer inside the system where financial decisions actually get made, rather than waiting for them to log into a portal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.openbankingtracker.com" rel="noopener noreferrer"&gt;Open banking&lt;/a&gt; regulations in Europe (PSD2), the UK, and increasingly the US (Section 1033) forced banks to build API capabilities for consumer data access. Those same APIs now serve ERP integration use cases. The plumbing exists. The question is who builds the last mile into the accounting system.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works technically
&lt;/h2&gt;

&lt;p&gt;ERP banking connects a bank's core systems to the customer's ERP or accounting platform. The goal is bidirectional data flow: the bank pushes transaction data and balances into the ERP, and the ERP sends payment instructions back to the bank.&lt;/p&gt;

&lt;p&gt;Datos Insights identifies five connectivity methods, roughly in order of maturity:&lt;/p&gt;

&lt;p&gt;File-based transfers using SFTP, BAI2, MT940, and ISO 20022 are the oldest method and still the most common at many institutions. The bank generates a file, uploads it to a server, and the ERP picks it up on a schedule. Reliable but slow. No real-time error handling. Frequent format mismatches between bank and ERP.&lt;/p&gt;

&lt;p&gt;Host-to-host connections are direct, dedicated links between a bank and a corporate customer's systems. Common in large enterprise relationships. Expensive to set up and maintain, which makes them impractical for mid-market customers.&lt;/p&gt;

&lt;p&gt;Bank APIs are the foundation of modern ERP banking. The bank publishes RESTful endpoints that the ERP or a middleware provider can call to retrieve balances, initiate payments, and pull transaction history. J.P. Morgan, HSBC, Deutsche Bank, PNC, and TD Bank all offer this kind of API access for corporate clients.&lt;/p&gt;

&lt;p&gt;ERP gateways and feeds are native connectivity features built into the ERP platforms themselves. Oracle Fusion Cloud ERP supports direct &lt;a href="https://www.apideck.com/blog/bank-api-integration" rel="noopener noreferrer"&gt;bank API integration&lt;/a&gt; from J.P. Morgan as a built-in capability. SAP has similar gateway functionality.&lt;/p&gt;

&lt;p&gt;ERP banking connectors are pre-built plugins from middleware providers like FISPAN. They connect a bank's services directly into specific ERP platforms. FISPAN's connector works with Oracle NetSuite, Sage Intacct, Microsoft Dynamics 365, QuickBooks Online, and Xero. The bank partners with FISPAN. The customer installs the connector. No custom development required.&lt;/p&gt;

&lt;p&gt;These methods are additive. A bank typically supports several simultaneously because its customer base spans different platforms and company sizes. A Fortune 500 on SAP might use host-to-host. A mid-market company on &lt;a href="https://www.apideck.com/integrations/oracle-netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; uses an ERP banking connector. A small business on &lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt; uses a bank feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does for the customer
&lt;/h2&gt;

&lt;p&gt;The use cases span the full commercial banking value chain. For a detailed breakdown with examples from J.P. Morgan, HSBC, Bank of America, Deutsche Bank, and others, see our companion post on &lt;a href="https://dev.to/blog/accounting-erp-integration-banks-use-cases"&gt;accounting and ERP integration for banks&lt;/a&gt;. Here is the summary.&lt;/p&gt;

&lt;p&gt;Payment initiation is the most visible use case. The customer creates a payment run inside their ERP and the bank processes it directly. No portal login required. TD Bank supports this for NetSuite, QuickBooks Online, Sage Intacct, and Dynamics 365 Business Central through its embedded banking product.&lt;/p&gt;

&lt;p&gt;Bank feeds and transaction sync follow closely. The bank pushes transaction data into the customer's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting system&lt;/a&gt; automatically. Challenger banks like Allica Bank and Monzo have turned this into a competitive differentiator, offering free integration with &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, QuickBooks, and Sage as part of their business banking products.&lt;/p&gt;

&lt;p&gt;Cash position reporting pulls balance and transaction data from all banking relationships into a single &lt;a href="https://dev.to/blog/treasury-management-systems-multi-bank-connectivity-integrations"&gt;treasury management system&lt;/a&gt; or ERP dashboard. No more logging into five different bank portals every morning.&lt;/p&gt;

&lt;p&gt;Automated reconciliation matches bank transactions against ERP records automatically. Exceptions get flagged for review. The line-by-line manual matching goes away.&lt;/p&gt;

&lt;p&gt;Expense sync lands corporate card transactions in the customer's accounting platform without anyone touching a spreadsheet. J.P. Morgan's Touchless Expense program and Cross River (powering Divvy/Bill.com) both work this way.&lt;/p&gt;

&lt;p&gt;Credit underwriting from accounting data connects banks and lenders to a business's accounting system through an API to pull real-time P&amp;amp;L, cash flow, and accounts receivable data. Plaid and Datos Insights found that 60% of US small business lenders now use some form of account data in their underwriting process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is building it
&lt;/h2&gt;

&lt;p&gt;The market has three layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Banks with direct integrations
&lt;/h3&gt;

&lt;p&gt;J.P. Morgan is furthest along. It became the first bank with direct integrated banking for Oracle Fusion Cloud ERP, and also partnered with FISPAN for its Treasury Ignition product covering NetSuite, Sage Intacct, and Dynamics 365. Over 1,000 corporates are live on Treasury Ignition.&lt;/p&gt;

&lt;p&gt;TD Bank launched its Embedded Banking product with FISPAN across four ERP platforms. HSBC and Deutsche Bank both partnered with FinLync for SAP connectivity. PNC, Citizens, KeyBank, Wells Fargo, BMO, and Santander all have FISPAN partnerships. BMO launched BMO Sync in early 2025 as the first fully embedded banking solution from a Canadian bank.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware and connector providers
&lt;/h3&gt;

&lt;p&gt;FISPAN is the clear market leader. Founded in 2016, it now has over 4,500 businesses on its platform processing more than $100 billion in annual payment volume. The company raised a $30 million Series B in mid-2025 from Canapi Ventures. Sage named FISPAN its recommended bank feeds solution for Sage Intacct, and over 1,000 businesses are now active on that integration alone, with adoption growing 118% year-over-year.&lt;/p&gt;

&lt;p&gt;FinLync focuses on the enterprise SAP segment. Cobase provides multi-bank connectivity for European corporates. Fides connects to over 13,000 banks globally for &lt;a href="https://dev.to/blog/treasury-management-systems-multi-bank-connectivity-integrations"&gt;treasury connectivity&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unified API providers
&lt;/h3&gt;

&lt;p&gt;For banks that need to connect to many accounting and ERP platforms at once, a &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;unified API&lt;/a&gt; provides a single normalized interface that maps to multiple downstream systems. Instead of building separate integrations for QuickBooks, Xero, Sage, NetSuite, &lt;a href="https://www.apideck.com/integrations/exact-online" rel="noopener noreferrer"&gt;Exact Online&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/myob" rel="noopener noreferrer"&gt;MYOB&lt;/a&gt;, and &lt;a href="https://www.apideck.com/integrations/dynamics-365-business-central" rel="noopener noreferrer"&gt;Microsoft Dynamics 365&lt;/a&gt;, the bank builds one integration and gets connectivity to all supported platforms through a single schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt; supports 45+ accounting and ERP connectors. The bank's engineering team writes to one API. Apideck handles the platform-specific translation, authentication, and lifecycle management. This approach is particularly valuable for the accounting data side of ERP banking: bank feeds, transaction sync, invoice reconciliation, and credit underwriting all require deep integration with the customer's accounting platform, and each platform has a different API with different data models.&lt;/p&gt;

&lt;p&gt;The distinction between middleware connectors and unified APIs matters here. FISPAN and FinLync solve the bank-to-ERP connectivity problem: getting payment capabilities and balance data into the ERP. Unified APIs like Apideck solve the ERP-to-accounting-data problem: reading and writing financial data across dozens of accounting platforms through a single integration. For banks building a full ERP banking stack, both layers come into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  The market
&lt;/h2&gt;

&lt;p&gt;Datos Insights estimates the ERP banking market at $11 billion to $19 billion, growing at nearly 10% annually.&lt;/p&gt;

&lt;p&gt;The retention argument alone justifies the investment. Once banking services are embedded in a customer's ERP workflow, switching banks becomes significantly more disruptive. FISPAN reports that banks on its platform see 30-50% deposit increases from connected clients. And 8% of FISPAN users are net-new to their commercial bank, meaning the embedded banking capability is winning accounts, not just retaining them.&lt;/p&gt;

&lt;p&gt;Datos Insights published a separate report specifically on monetizing ERP banking, exploring pricing models from premium connectivity fees to transaction-based billing to value-added bundling within commercial banking packages.&lt;/p&gt;

&lt;p&gt;J.P. Morgan's 2026 payment trends report found that 88% of financial professionals consider direct bank connectivity very or extremely beneficial. 85% said they would likely switch banks to get it. At that volume of demand, banks don't get to treat this as optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  The accounting layer problem
&lt;/h2&gt;

&lt;p&gt;Most of the attention in ERP banking goes to the bank connectivity side. How does the bank's API connect to the ERP platform? That matters, but it is only half the problem.&lt;/p&gt;

&lt;p&gt;The other half is the &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting layer&lt;/a&gt;. A bank can push transactions into an ERP all day long, but if those transactions do not reconcile against the customer's chart of accounts, match invoices, or flow into journal entries correctly, the integration creates more problems than it solves.&lt;/p&gt;

&lt;p&gt;This is where fragmentation hits hardest. Each accounting platform has its own API with its own data model for transactions, its own authentication scheme, its own field naming conventions. A bank that wants to support QuickBooks Online, Xero, Sage Intacct, NetSuite, &lt;a href="https://www.apideck.com/integrations/freshbooks" rel="noopener noreferrer"&gt;FreshBooks&lt;/a&gt;, and Dynamics 365 needs six different accounting integrations. Building and maintaining those one at a time is expensive. Every engineer working on &lt;a href="https://dev.to/blog/erp-api-integration"&gt;accounting platform connectors&lt;/a&gt; is an engineer not working on core banking products. This is the problem &lt;a href="https://dev.to/blog/what-is-a-unified-api"&gt;unified accounting APIs&lt;/a&gt; exist to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes
&lt;/h2&gt;

&lt;p&gt;ERP banking is a platform shift in commercial banking, not a feature.&lt;/p&gt;

&lt;p&gt;The banks that do it well will become invisible to their customers in the best possible way. The middleware providers and &lt;a href="https://www.apideck.com/industries/banking" rel="noopener noreferrer"&gt;unified API platforms&lt;/a&gt; connecting banks, ERPs, and accounting systems are the infrastructure layer making that transition possible.&lt;/p&gt;

&lt;p&gt;For any bank evaluating its ERP banking strategy, the demand case is settled. The remaining problem is how to reach broad accounting platform coverage without burning through engineering resources on one-at-a-time integrations. The largest banks can build natively to Oracle and SAP. Everyone else needs the combination of ERP banking connectors for the bank-to-ERP pipe and &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;unified accounting APIs&lt;/a&gt; for the accounting data pipe.&lt;/p&gt;

&lt;p&gt;Apideck's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Accounting API&lt;/a&gt; connects to 45+ accounting and ERP platforms through a single integration. If your bank needs to build accounting integrations at scale, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;get started here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>erpbanking</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Bank API Integration: How Banks Connect to Accounting and ERP Systems</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Wed, 15 Jul 2026 16:11:22 +0000</pubDate>
      <link>https://dev.to/apideck/bank-api-integration-how-banks-connect-to-accounting-and-erp-systems-4ga8</link>
      <guid>https://dev.to/apideck/bank-api-integration-how-banks-connect-to-accounting-and-erp-systems-4ga8</guid>
      <description>&lt;p&gt;Banks no longer compete on branch locations or interest rates alone. The fastest-growing differentiator in commercial banking is how well a bank plugs into the accounting and ERP systems its customers already use every day. This shift is often called &lt;a href="https://dev.to/blog/accounting-erp-integration-banks-use-cases"&gt;ERP banking&lt;/a&gt;, and it is reshaping how commercial banks compete for business customers.&lt;/p&gt;

&lt;p&gt;Bank API integration is the technical bridge that makes this possible. It connects a bank's core systems to third-party accounting platforms, ERPs, and financial applications through standardized interfaces. When done well, it eliminates manual file transfers, enables real-time cash visibility, and turns the bank into an invisible layer inside the customer's existing workflow.&lt;/p&gt;

&lt;p&gt;This guide covers how bank API integration works, the use cases driving adoption, the engineering challenges involved, and the approaches available for building and scaling these integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is bank API integration?
&lt;/h2&gt;

&lt;p&gt;Bank API integration refers to any API-based connection between a bank and a third-party application. The third party is typically an ERP system like Oracle NetSuite or SAP, an accounting platform like QuickBooks Online or Xero, or a fintech application that handles payments, expense management, or treasury operations.&lt;/p&gt;

&lt;p&gt;Once the connection is established, financial data flows between the bank and the third-party system on a defined cadence. That data can include transaction records, account balances, payment instructions, invoice details, and reconciliation status.&lt;/p&gt;

&lt;p&gt;There are two distinct perspectives on bank API integration, and they matter because the technical requirements differ:&lt;/p&gt;

&lt;p&gt;The bank's perspective. A bank builds integrations with accounting and ERP platforms to offer embedded financial services to its business customers. The goal is to reduce churn, win new commercial accounts, and differentiate against competitors who still rely on portal-based workflows. J.P. Morgan, HSBC, TD Bank, and PNC all run embedded banking programs that connect directly into customer ERP environments.&lt;/p&gt;

&lt;p&gt;The business's perspective. A company connects its bank accounts to its own accounting or ERP system to automate cash management, reconciliation, and payment workflows. The goal is operational efficiency. Finance teams want real-time visibility into bank balances without logging into separate portals, and they want payment runs initiated from within their system of record.&lt;/p&gt;

&lt;p&gt;Both perspectives converge on the same technical problem: connecting bank systems to accounting platforms at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why APIs beat the alternatives
&lt;/h2&gt;

&lt;p&gt;Banks and businesses have historically relied on four methods to move financial data between systems: manual entry, file-based transfers, screen scraping, and APIs. Here is why APIs are winning.&lt;/p&gt;

&lt;p&gt;File-based transfers (SFTP/BAI2/MT940) still dominate in legacy commercial banking. The bank generates a file in BAI2 or MT940 format, uploads it to an SFTP server, and the customer's ERP system picks it up on a schedule. This works, but it introduces latency (often 24 hours), requires manual intervention when file formats change, and creates reconciliation gaps when transactions arrive out of order. File-based integrations also lack real-time error handling. If a payment file contains a malformed record, the entire batch may fail silently.&lt;/p&gt;

&lt;p&gt;Screen scraping extracts data from a bank's online portal by simulating user interactions. It breaks every time the bank updates its UI. It also creates security risks because it requires storing and transmitting user credentials. Most banks actively block scraping, and regulatory frameworks like PSD2 in Europe have replaced it with dedicated API access.&lt;/p&gt;

&lt;p&gt;APIs solve these problems. They provide structured, authenticated, real-time access to financial data. Authentication is handled through OAuth 2.0 or API keys with scoped permissions. Data is encrypted in transit. Rate limits prevent abuse. And when something fails, the API returns a structured error response that the consuming application can handle programmatically.&lt;/p&gt;

&lt;p&gt;The fourth option, manual entry, still exists in small businesses but is not a serious integration method. It does not scale, it introduces human error, and it consumes finance team hours that could be spent on higher-value work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of bank APIs
&lt;/h2&gt;

&lt;p&gt;Not all bank APIs are the same. The access model and scope vary significantly depending on the bank's strategy and regulatory environment.&lt;/p&gt;

&lt;p&gt;Private APIs are internal to the bank. They connect the bank's own systems, such as the core banking platform, the online portal, and the mobile app. Third parties cannot access these. They matter for bank API integration only insofar as they constrain what data the bank can expose through its other APIs.&lt;/p&gt;

&lt;p&gt;Partner APIs are shared with selected third parties under a contractual agreement. A bank might expose payment initiation and account information endpoints to a specific ERP vendor or fintech partner. FISPAN, for example, partners with banks like J.P. Morgan, PNC, and TD Bank to embed banking services inside ERP platforms through partner API access. These integrations are typically bespoke and require a formal relationship.&lt;/p&gt;

&lt;p&gt;Open APIs are available to any authorized developer. In the EU, PSD2 mandates that banks provide &lt;a href="https://www.openbankingtracker.com/open-banking-api" rel="noopener noreferrer"&gt;open banking APIs&lt;/a&gt; for account information (AIS) and payment initiation (PIS) to licensed third-party providers. The UK's Open Banking Implementation Entity defined standardized API specifications that the nine largest UK banks must support. In the US, the CFPB's Section 1033 rulemaking is pushing toward formal consumer data access standards, though the market currently relies more on data aggregators like Plaid and MX than on bank-published open APIs. The &lt;a href="https://www.openbankingtracker.com" rel="noopener noreferrer"&gt;Open Banking Tracker&lt;/a&gt; tracks 54,000+ financial institutions across 30+ jurisdictions, providing a comprehensive view of which banks offer open API access and under what regulatory framework.&lt;/p&gt;

&lt;p&gt;Bank-as-a-Service (BaaS) APIs are a newer category. Companies like Solaris, Column, and Treasury Prime provide full-stack banking APIs that non-bank companies can use to offer financial products. Samsung Pay, for example, runs on Solaris APIs. This is adjacent to traditional bank API integration but follows a different pattern: instead of connecting to an existing bank, the company embeds a bank's infrastructure through APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases driving bank API integration
&lt;/h2&gt;

&lt;p&gt;The market for bank-ERP integration is estimated at $11 billion to $19 billion and growing at nearly 10% annually, according to Datos Insights research. Here are the use cases driving that growth. (For a deeper dive with specific bank examples from J.P. Morgan, HSBC, Deutsche Bank, and others, see our companion post on &lt;a href="https://dev.to/blog/accounting-erp-integration-banks-use-cases"&gt;accounting and ERP integration for banks&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Bank feeds and transaction sync
&lt;/h3&gt;

&lt;p&gt;This is the most common use case and the foundation for everything else. The bank sends transaction data (debits, credits, balances) to the customer's accounting system automatically. In accounting terms, this is the bank feed.&lt;/p&gt;

&lt;p&gt;Challenger banks like Allica Bank and Monzo have turned accounting integration into a competitive weapon for business banking. Allica provides free integration with &lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, and Sage as part of its Business Rewards Account. Monzo gates accounting integration behind its paid business tiers. Both approaches reflect the same reality: bank feeds are no longer an optional add-on.&lt;/p&gt;

&lt;p&gt;For the bank building these integrations, the challenge is supporting the dozen-plus accounting platforms its customers actually use. QuickBooks Online, Xero, Sage Intacct, FreshBooks, Zoho Books, Wave, and &lt;a href="https://www.apideck.com/integrations/dynamics-365-business-central" rel="noopener noreferrer"&gt;Microsoft Dynamics 365&lt;/a&gt; each have different API schemas, authentication flows, and data models for representing transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment initiation from ERP
&lt;/h3&gt;

&lt;p&gt;Instead of logging into a bank portal to initiate payments, the customer creates a payment run inside their ERP and the bank processes it directly. TD Bank's Embedded Banking product supports this for NetSuite, QuickBooks Online, Sage Intacct, and Microsoft Dynamics 365 Business Central. Customers can initiate ACH, wire, and check payments without leaving their ERP.&lt;/p&gt;

&lt;p&gt;For the bank, this is a retention play. Once payment initiation is embedded in a customer's ERP workflow, switching banks becomes significantly more disruptive. For the customer, it eliminates the dual-entry problem where payment details have to be entered in both the ERP and the bank portal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoice reconciliation
&lt;/h3&gt;

&lt;p&gt;A vendor creates an invoice through their bank, sends it to the customer, and the invoice data syncs to the vendor's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting system&lt;/a&gt;. When the customer pays, the payment data flows back into the accounting system and matches against the outstanding invoice. The vendor's accounting team reconciles without manual intervention.&lt;/p&gt;

&lt;p&gt;This workflow requires bidirectional sync between the bank and the accounting platform. The bank needs to push invoice metadata (amount, date, currency, due date) to the accounting system on creation, and then update the invoice status when payment is received.&lt;/p&gt;

&lt;h3&gt;
  
  
  Corporate card provisioning and expense sync
&lt;/h3&gt;

&lt;p&gt;Banks that offer corporate card programs can integrate with their customers' HR and accounting systems to automate card provisioning. When a new employee is added to the HRIS, the bank provisions a corporate card with spending limits based on the employee's role, department, and location. When the employee is terminated, the card is deactivated automatically.&lt;/p&gt;

&lt;p&gt;On the expense side, corporate card transactions sync directly into the customer's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting platform&lt;/a&gt;. J.P. Morgan's Touchless Expense program and Cross River (powering Divvy/Bill.com) both operate this way. The bank pushes categorized transaction data into the accounting system, eliminating manual expense reports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credit underwriting from accounting data
&lt;/h3&gt;

&lt;p&gt;This may be the most transformative use case. By accessing a business's real-time accounting data (profit and loss statements, cash flow, accounts receivable aging, and outstanding obligations), lenders can make faster and more accurate credit decisions than traditional methods allow.&lt;/p&gt;

&lt;p&gt;Research by Plaid and Datos Insights found that 60% of US small business lenders now use some form of account data in their underwriting process. This is one of the clearest examples of &lt;a href="https://www.openbankingtracker.com/open-finance-map" rel="noopener noreferrer"&gt;open finance&lt;/a&gt; in action: instead of relying on annual financial statements and manual document collection, the lender connects to the business's accounting system through an API and pulls real-time financial data.&lt;/p&gt;

&lt;p&gt;For banks building this capability, the integration needs to support read access to the full chart of accounts, journal entries, invoices, bills, and bank transactions across whatever accounting platform the applicant uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cash position reporting
&lt;/h3&gt;

&lt;p&gt;Treasury teams at mid-market and enterprise companies need real-time visibility into cash positions across multiple bank accounts, entities, and currencies. Without an integration, this means logging into multiple bank portals, downloading statements, and consolidating data in a spreadsheet.&lt;/p&gt;

&lt;p&gt;With bank API integration, balance and transaction data from all banking relationships flows into a single treasury management system or ERP. Kyriba, for example, connects to over 9,900 banks to provide this kind of unified cash visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineering challenge
&lt;/h2&gt;

&lt;p&gt;The use cases are clear. The engineering challenge is what makes bank API integration hard in practice.&lt;/p&gt;

&lt;p&gt;Each accounting platform and ERP has its own API with its own data model, authentication scheme, rate limits, and field conventions. A bank that wants to support QuickBooks Online, Xero, Sage Intacct, &lt;a href="https://www.apideck.com/integrations/oracle-netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, Microsoft Dynamics 365, and FreshBooks needs to build and maintain six separate integrations.&lt;/p&gt;

&lt;p&gt;Each of those integrations involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing the platform's authentication flow (OAuth 2.0 for most, but the details vary)&lt;/li&gt;
&lt;li&gt;Mapping the bank's internal data model to the platform's schema for transactions, invoices, contacts, accounts, and journal entries&lt;/li&gt;
&lt;li&gt;Handling pagination, rate limits, webhook subscriptions, and retry logic&lt;/li&gt;
&lt;li&gt;Managing ongoing API version changes, field deprecations, and breaking changes&lt;/li&gt;
&lt;li&gt;Testing against sandbox environments, which not all platforms provide equally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Datos Insights research across 1,000+ corporate users in 11 countries found that more than one in four corporate treasurers will likely switch their primary financial institution within two years because a competitor offers better technology integration. The stakes are high, but the engineering cost of building and maintaining integrations one at a time is equally high.&lt;/p&gt;

&lt;p&gt;Banks that try to build everything in-house face a painful tradeoff: every engineer working on accounting platform integrations is an engineer not working on core banking products. And the integration surface area keeps growing as new accounting platforms gain market share and existing platforms evolve their APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How unified APIs solve the fragmentation problem
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;unified API&lt;/a&gt; provides a single normalized interface that maps to multiple downstream platforms. Instead of building separate integrations for QuickBooks, Xero, Sage, NetSuite, and the rest, the bank builds one integration against the unified API and gets connectivity to all supported platforms through a single schema.&lt;/p&gt;

&lt;p&gt;This approach works because accounting platforms share a common conceptual model despite differing implementations. Every accounting system has some version of accounts, transactions, invoices, bills, contacts, journal entries, and tax rates. The unified API abstracts over the implementation differences while preserving access to the underlying data.&lt;/p&gt;

&lt;p&gt;For banks, the advantages are significant:&lt;/p&gt;

&lt;p&gt;Speed. One integration instead of six or twelve. A bank can go from zero accounting platform connectivity to broad coverage in weeks instead of months.&lt;/p&gt;

&lt;p&gt;Maintenance. The unified API provider handles downstream API changes, field mapping updates, and authentication flow changes. The bank's engineering team stays focused on its core product.&lt;/p&gt;

&lt;p&gt;Coverage. A unified accounting API like &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's&lt;/a&gt; supports 40+ accounting and ERP connectors through a single integration. As new platforms gain market share, the bank gets access without additional engineering work.&lt;/p&gt;

&lt;p&gt;Normalized data. The bank works with a single schema for invoices, transactions, and journal entries regardless of which accounting platform the customer uses. This simplifies downstream processing, reporting, and analytics.&lt;/p&gt;

&lt;p&gt;The tradeoff is control. A unified API introduces a dependency and an abstraction layer. Banks that need deep, platform-specific functionality (custom fields, platform-specific workflows, advanced reporting APIs) may find that the unified model does not cover every edge case. The right approach depends on the bank's integration needs, engineering capacity, and the breadth of platforms it needs to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approaches to building bank API integration
&lt;/h2&gt;

&lt;p&gt;There are three primary approaches. Each involves a different set of tradeoffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build native integrations in-house
&lt;/h3&gt;

&lt;p&gt;The bank's engineering team builds and maintains each integration directly against the target platform's API.&lt;/p&gt;

&lt;p&gt;This gives the bank full control over data mapping, sync cadence, error handling, and feature depth. It also means the bank can build highly customized integrations that match its customers' exact requirements.&lt;/p&gt;

&lt;p&gt;The downside is cost and speed. Building a production-quality integration with a single accounting platform takes weeks to months, and maintaining it is an ongoing engineering commitment. Banks that choose this path typically support only two or three platforms and leave the rest of the market unserved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expose API endpoints for customers to build against
&lt;/h3&gt;

&lt;p&gt;Larger banks often publish their own APIs and let customers or their ERP vendors build the integration. This shifts the engineering burden to the customer side.&lt;/p&gt;

&lt;p&gt;The bank avoids building and maintaining integrations itself, but the customer experience suffers. Customers may take months to build to the bank's endpoints, and ongoing maintenance falls on their engineering teams. For small and mid-market businesses without dedicated integration engineers, this approach is not viable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a unified API
&lt;/h3&gt;

&lt;p&gt;The bank integrates once with a unified API provider and gets normalized access to multiple accounting and ERP platforms. &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt;, for example, provides a single integration point that connects to &lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks Online&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/sage-intacct" rel="noopener noreferrer"&gt;Sage Intacct&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, FreshBooks, Exact Online, &lt;a href="https://www.apideck.com/integrations/myob" rel="noopener noreferrer"&gt;MYOB&lt;/a&gt;, and &lt;a href="https://www.apideck.com/integrations" rel="noopener noreferrer"&gt;many others&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the fastest path to broad coverage and the lowest ongoing maintenance burden. The bank's engineering team writes to one API schema, and the unified API provider handles the platform-specific translation, authentication, and API lifecycle management.&lt;/p&gt;

&lt;p&gt;Banks that need to move quickly, support a wide range of customer accounting platforms, and keep their engineering team focused on core banking products should evaluate the unified API approach seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate a bank API integration strategy
&lt;/h2&gt;

&lt;p&gt;The right approach depends on several factors specific to the bank's situation.&lt;/p&gt;

&lt;p&gt;How many accounting platforms do your customers use? If the answer is two (say, QuickBooks and Xero), native integrations may be viable. If the answer is eight or more, building natively becomes prohibitively expensive.&lt;/p&gt;

&lt;p&gt;How fast do you need to go live? If there is competitive pressure to launch ERP-embedded banking quickly, a unified API cuts time-to-market from months to weeks.&lt;/p&gt;

&lt;p&gt;What is the depth of integration required? Simple bank feeds require a narrow API surface. Full bidirectional sync with payment initiation, invoice management, and journal entry creation requires deeper integration. Make sure whatever approach you choose supports the data objects and operations your use cases demand.&lt;/p&gt;

&lt;p&gt;What is the total cost of ownership? Factor in initial build time, ongoing maintenance, developer hiring, sandbox access fees, and the opportunity cost of engineers not working on core banking products.&lt;/p&gt;

&lt;p&gt;Does the provider offer sandbox environments? Testing accounting integrations against live customer data is risky. Sandbox environments with realistic test data are essential for development and QA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open banking is not the same thing
&lt;/h2&gt;

&lt;p&gt;Open banking and bank API integration overlap but are not the same thing.&lt;/p&gt;

&lt;p&gt;Open banking is a regulatory and industry framework that requires banks to provide API access to authorized third parties. It is driven by regulation (&lt;a href="https://www.openbankingtracker.com/regulations" rel="noopener noreferrer"&gt;PSD2 in Europe&lt;/a&gt;, Open Banking in the UK, Section 1033 in the US) and focuses on consumer data portability and payment initiation. The scope is expanding toward &lt;a href="https://www.openbankingtracker.com/open-finance" rel="noopener noreferrer"&gt;open finance&lt;/a&gt;, which extends the same principles beyond payments and accounts to insurance, investments, pensions, and lending data.&lt;/p&gt;

&lt;p&gt;Bank API integration is a broader category that includes any API-based connection between a bank and a third-party system. It covers open banking use cases but also extends to ERP integrations, accounting platform connections, treasury management, and embedded banking services that are not mandated by regulation.&lt;/p&gt;

&lt;p&gt;A bank can have a robust open banking API program and still lack proper integrations with the accounting platforms its commercial customers use. The two capabilities serve different customer segments and different business objectives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The direction is clear. According to Datos Insights, more than one in four corporate treasurers will likely switch banks within two years if a competitor offers better technology integration. Banks that embrace &lt;a href="https://dev.to/blog/accounting-erp-integration-banks-use-cases"&gt;ERP banking&lt;/a&gt; and embed their services inside the accounting workflows their customers already rely on will retain and grow their commercial banking relationships. Banks that force customers to use separate portals and manual file transfers will lose them.&lt;/p&gt;

&lt;p&gt;The technical infrastructure to make this work exists today. The question for any bank is whether to build it all in-house, piece by piece, or use a &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;unified accounting API&lt;/a&gt; to get there faster.&lt;/p&gt;

&lt;p&gt;Apideck's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Accounting API&lt;/a&gt; connects to 30+ accounting and ERP platforms through a single integration. If your bank needs to build accounting integrations at scale, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;get started here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>bankapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Treasury Accounting: Why Every Fintech That Moves Money Needs ERP Integration</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Tue, 14 Jul 2026 11:41:04 +0000</pubDate>
      <link>https://dev.to/apideck/treasury-accounting-why-every-fintech-that-moves-money-needs-erp-integration-5bpg</link>
      <guid>https://dev.to/apideck/treasury-accounting-why-every-fintech-that-moves-money-needs-erp-integration-5bpg</guid>
      <description>&lt;p&gt;Your treasury product is working. Customers are depositing funds, earning yield, and paying bills from a single platform. Cash management has never been smoother.&lt;/p&gt;

&lt;p&gt;Then the finance team calls. They need to reconcile treasury activity in QuickBooks. Or NetSuite. Or Xero. And suddenly your product creates more manual work than it eliminates, because there is no clean path from your treasury ledger to their general ledger.&lt;/p&gt;

&lt;p&gt;This is the treasury accounting problem. And it is quietly becoming the biggest gap in fintech product stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What treasury accounting actually means
&lt;/h2&gt;

&lt;p&gt;Treasury accounting is the process of recording, classifying, and &lt;a href="https://www.numeric.io/blog/account-reconciliation-software" rel="noopener noreferrer"&gt;reconciling all financial activity&lt;/a&gt; that flows through a company's treasury function. That includes cash deposits, withdrawals, investment earnings, internal transfers between accounts, and any interest or yield payouts.&lt;/p&gt;

&lt;p&gt;For a traditional corporate treasury team, this work happens inside an ERP or dedicated &lt;a href="https://www.apideck.com/blog/treasury-management-systems-multi-bank-connectivity-integrations" rel="noopener noreferrer"&gt;treasury management system&lt;/a&gt;. Journal entries are posted for every movement of cash. GL accounts are mapped for each treasury account. The month-end close process depends on this data being accurate and timely.&lt;/p&gt;

&lt;p&gt;But the game has changed. A growing number of companies now manage treasury activity through fintech platforms rather than through their bank directly. Spend management tools, neobanks, and vertical SaaS platforms now offer treasury features that let customers sync business account activity and investment earnings into their ERP as journal entries. That is not a nice-to-have feature. It is a prerequisite for adoption by any company with a finance team that actually closes their books.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reconciliation gap in modern treasury
&lt;/h2&gt;

&lt;p&gt;Here is what happens when a fintech platform handles treasury but does not integrate with accounting systems.&lt;/p&gt;

&lt;p&gt;The platform tracks every deposit, withdrawal, and earnings payout internally. The customer's finance team then needs to get that data into their ERP for reconciliation. Without an integration, they are left exporting CSV or OFX files, manually uploading them, and then matching transactions by hand. For companies running multiple treasury accounts with daily activity, this becomes a serious time sink.&lt;/p&gt;

&lt;p&gt;The problem gets worse when you add &lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;bank feeds&lt;/a&gt; into the picture. Bank feeds provide a statement-level view of transactions for matching and verification in the ERP. But the accounting sync creates the actual ledger entries. If both are running but the data does not match cleanly, finance teams end up with duplicate transactions, unmatched entries, and a reconciliation nightmare.&lt;/p&gt;

&lt;p&gt;The best implementations address this by building two distinct sync paths: an accounting sync that pushes journal entries into the ERP, and a separate bank feed for statement-level reconciliation. The key detail is that these two paths serve different purposes. The accounting sync creates the book-side records. The bank feed provides the bank-side records for verification. Both are needed, and they should not be confused or combined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for any platform that moves money
&lt;/h2&gt;

&lt;p&gt;If you are building a fintech product that handles customer funds, whether that is a business account, an investment product, a payment platform, or a &lt;a href="https://www.apideck.com/blog/money-movement-infrastructure-fintech-ledger-as-a-service" rel="noopener noreferrer"&gt;cash management tool&lt;/a&gt;, treasury accounting is your responsibility. Not in the sense that you do the accounting for your customers. But in the sense that you need to give them the tools to account for what happens on your platform.&lt;/p&gt;

&lt;p&gt;That means your product needs to support a few things.&lt;/p&gt;

&lt;p&gt;First, GL account mapping. Customers need to link their treasury accounts in your product to corresponding accounts in their ERP. A business account maps to one GL account. An earnings or investment account maps to another. Without this mapping, there is no way to automatically post transactions to the right place.&lt;/p&gt;

&lt;p&gt;Second, journal entry sync. Cash transfers and earnings payouts should sync into the ERP as proper journal entries, not as generic transactions that need to be reclassified. The more structured the data you send, the less manual work the customer's finance team has to do.&lt;/p&gt;

&lt;p&gt;Third, bank feed support. Even with accounting sync, finance teams need a bank-side view for reconciliation. Offering OFX or CSV exports, or direct &lt;a href="https://developers.apideck.com/guides/bank-feeds-xero" rel="noopener noreferrer"&gt;bank feed connections via Xero&lt;/a&gt; or similar providers, gives them the second side of the matching equation.&lt;/p&gt;

&lt;p&gt;Fourth, sync controls and visibility. Finance teams need the ability to manually trigger syncs for unsynced transfers, view sync history, and see clear error messages when something fails. This is not just a UX concern. It is an audit concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building treasury accounting with APIs: a practical walkthrough
&lt;/h2&gt;

&lt;p&gt;Let's get concrete. If your platform processes treasury transactions and you want to sync them into your customers' accounting systems, here is what the integration looks like using a &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;unified accounting API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Retrieve the customer's chart of accounts
&lt;/h3&gt;

&lt;p&gt;Before you can post journal entries, you need to know which GL accounts exist in the customer's ERP. The ledger accounts endpoint returns the full chart of accounts so your product can let users map their treasury accounts to the right GL codes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/unify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-app-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve the customer's chart of accounts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ledgerAccounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or 'xero', 'netsuite', 'sage-intacct', etc.&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// filter for bank/cash accounts&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Returns normalized ledger accounts across any ERP&lt;/span&gt;
&lt;span class="c1"&gt;// Use these IDs to map treasury accounts to GL codes&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nominal_code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works identically whether the customer uses QuickBooks, Xero, NetSuite, or Sage Intacct. The unified API normalizes the response so your mapping UI does not need ERP-specific logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create journal entries for treasury activity
&lt;/h3&gt;

&lt;p&gt;When a deposit, withdrawal, or earnings payout occurs on your platform, post it to the customer's ERP as a journal entry. Each entry needs balanced debit and credit lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sync a treasury deposit as a journal entry&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;journalEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journalEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;journalEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Treasury Deposit - Business Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;posted_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-15T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transfer from linked bank to treasury business account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;treasury-business-account-gl-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// mapped in Step 1&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deposit to treasury business account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linked-bank-account-gl-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// source bank account&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transfer from operating account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Journal entry created: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;journalEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Sync earnings and yield payouts
&lt;/h3&gt;

&lt;p&gt;Investment earnings and yield payouts need their own journal entries, typically crediting a revenue or interest income account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sync an earnings payout from the investment account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;earningsEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journalEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// same code, different ERP&lt;/span&gt;
  &lt;span class="na"&gt;journalEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Treasury Earnings - Q1 2026&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;posted_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-31T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Quarterly earnings payout from investment account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1825.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;business-account-gl-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Earnings received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1825.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interest-income-gl-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// revenue/earnings account&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Investment earnings - money market fund&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Push bank feed statements for reconciliation
&lt;/h3&gt;

&lt;p&gt;For ERPs that support bank feeds (like &lt;a href="https://developers.apideck.com/guides/bank-feeds-xero" rel="noopener noreferrer"&gt;Xero bank feeds&lt;/a&gt;), you can push transaction statements directly. This gives the finance team the bank-side view they need to match against the journal entries you posted above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a bank feed account (one-time setup)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankFeedAccounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bankFeedAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;source_account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-platform-account-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;target_account_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Treasury Business Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;target_account_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;98765432&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bank_account_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Push a bank feed statement with transactions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankFeedStatements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bankFeedStatement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bank_feed_account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feedAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;start_balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;end_balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;151825.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;posted_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-15T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transfer from operating account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;posted_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-31T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Q1 2026 earnings payout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1825.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The finance team opens their ERP's reconciliation module, sees the bank feed transactions, and matches them against the journal entries your platform already synced. No CSV exports. No manual uploads. Books close on schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The integration challenge: every ERP is different
&lt;/h2&gt;

&lt;p&gt;The real complexity is not in the accounting logic. It is in the integrations.&lt;/p&gt;

&lt;p&gt;QuickBooks Online, NetSuite, Xero, Sage Intacct, and QuickBooks Desktop all handle journal entries, chart of accounts, and bank reconciliation differently. Each has its own API with different authentication models, data formats, rate limits, and sync behaviors. Some have additional quirks: Microsoft Dynamics 365 Business Central, for example, does not even expose a standalone journal entry object via its API. It uses general journal batches and lines, requiring batch-level posting logic that differs fundamentally from how QuickBooks or Xero handle the same operation.&lt;/p&gt;

&lt;p&gt;Building a direct integration to even one of these systems takes weeks of engineering time. Building and maintaining integrations to all of them is a full-time job.&lt;/p&gt;

&lt;p&gt;This is where most fintech companies face a strategic decision. Do you build and maintain direct integrations to each ERP? Do you support CSV exports and let customers handle the mapping themselves? Or do you use a &lt;a href="https://www.apideck.com/blog/what-is-a-unified-api" rel="noopener noreferrer"&gt;unified API&lt;/a&gt; that normalizes the differences across platforms?&lt;/p&gt;

&lt;p&gt;The first option gives you the most control but the highest maintenance burden. The second option is the cheapest to build but creates the worst customer experience. The third option lets you ship treasury accounting to all major ERPs through a single integration, but it requires choosing the right abstraction layer.&lt;/p&gt;

&lt;p&gt;As the code examples above illustrate, the unified API approach means your &lt;code&gt;journalEntries.create&lt;/code&gt; call works the same way whether the customer is on QuickBooks, Xero, NetSuite, or Exact Online. The API handles the translation, authentication, and field mapping under the hood. For a deeper comparison of the &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;top accounting APIs&lt;/a&gt;, the differences in how each platform handles journal entries and bank feeds become even more apparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good treasury accounting looks like
&lt;/h2&gt;

&lt;p&gt;The gold standard for treasury accounting in a fintech product looks something like this.&lt;/p&gt;

&lt;p&gt;When a customer connects their ERP, your platform automatically discovers or creates the relevant GL accounts for treasury activity. Deposits, withdrawals, earnings payouts, and internal transfers each map to specific accounts. As transactions occur, they sync into the ERP as properly coded journal entries with no manual intervention required.&lt;/p&gt;

&lt;p&gt;In parallel, the platform provides a bank feed or exportable statement that the customer can use to reconcile the ledger entries against actual account activity. The finance team opens their reconciliation module, sees the matched entries, and confirms them. Month-end close happens on schedule.&lt;/p&gt;

&lt;p&gt;When something fails, the customer sees a clear error with a path to resolution. Sync history is downloadable. Unsynced transfers are flagged and can be retried. The finance team has full visibility and control.&lt;/p&gt;

&lt;p&gt;This is not a luxury feature. For companies that handle customer funds, this is the bare minimum to serve any customer with a real accounting function.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture: treasury is becoming a platform feature
&lt;/h2&gt;

&lt;p&gt;Treasury functionality, which used to live exclusively inside banks and standalone TMS platforms, is increasingly being embedded into fintech products. Spend management platforms, payment processors, neobanks, and vertical SaaS companies are all adding cash management and yield features.&lt;/p&gt;

&lt;p&gt;But every one of these products eventually hits the same wall: the customer's finance team needs the data in their ERP. If the integration is not there, adoption stalls. If the integration is manual (CSV exports, copy-pasting), adoption happens but with resentment. If the integration is automated and reliable, treasury features become a genuine differentiator.&lt;/p&gt;

&lt;p&gt;The concept of &lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;Open Accounting&lt;/a&gt;, where businesses expect their financial data to be accessible through standardized APIs regardless of which platform they use, is accelerating this shift. Just as Open Banking standardized access to bank account data, Open Accounting is pushing toward standardized access to ledger data across ERPs and accounting platforms. For treasury platforms, this means the infrastructure to sync journal entries, ledger accounts, and bank feeds across systems is becoming more accessible, but only if you invest in building it.&lt;/p&gt;

&lt;p&gt;The companies that figure out treasury accounting early, and invest in proper ERP integrations from the start, will be the ones that capture the most value from this shift. The ones that treat it as an afterthought will keep losing deals to competitors who made &lt;a href="https://dev.to/blog/accounting-integration"&gt;accounting sync a day-one feature&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;If you are building or operating a fintech platform that moves customer money and you have not yet addressed treasury accounting, here is where to start.&lt;/p&gt;

&lt;p&gt;Audit your current state. How do customers currently get treasury data into their ERP? If the answer is CSV exports or manual entry, you have a gap to close.&lt;/p&gt;

&lt;p&gt;Talk to your customers' finance teams. Not the product buyer, not the admin. Talk to the person who closes the books each month. Ask them what data they need, in what format, and when they need it.&lt;/p&gt;

&lt;p&gt;Evaluate your integration strategy. For most platforms, building direct integrations to every ERP is not practical. Look at &lt;a href="https://dev.to/accounting-api"&gt;unified API providers&lt;/a&gt; that support journal entries, chart of accounts, and bank transaction objects across multiple accounting platforms.&lt;/p&gt;

&lt;p&gt;Start with the big three. QuickBooks Online, Xero, and NetSuite cover the vast majority of small and mid-market companies. If you can sync treasury activity into these three systems, you have addressed 80% or more of your customer base.&lt;/p&gt;

&lt;p&gt;Build sync visibility from the start. Do not bolt on error handling and sync history later. Finance teams need to trust the integration, and trust comes from transparency.&lt;/p&gt;

&lt;p&gt;Treasury accounting is not a feature request. It is infrastructure. Build it like infrastructure, and it will pay dividends across your entire product.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;Bank Feeds API Integration: Why You Can't Afford to Skip This Feature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/treasury-management-systems-multi-bank-connectivity-integrations" rel="noopener noreferrer"&gt;Treasury Management Systems: Multi-Bank Connectivity Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/money-movement-infrastructure-fintech-ledger-as-a-service" rel="noopener noreferrer"&gt;Money Movement Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;What is Open Accounting?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate with in 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: Apideck's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt; supports journal entries, chart of accounts, bank feeds, and bank transaction sync across QuickBooks, Xero, NetSuite, Sage, and &lt;a href="https://www.apideck.com/integrations" rel="noopener noreferrer"&gt;40+ other accounting and ERP platforms&lt;/a&gt;. If you are building treasury accounting features, &lt;a href="https://dev.to/contact"&gt;get in touch&lt;/a&gt; or explore the &lt;a href="https://developers.apideck.com/apis/accounting/reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; to see how it works.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>fintech</category>
      <category>product</category>
      <category>saas</category>
    </item>
    <item>
      <title>Multi-Entity General Ledger Integration: What Changes When You Build Accounting APIs</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 13 Jul 2026 21:21:31 +0000</pubDate>
      <link>https://dev.to/apideck/multi-entity-general-ledger-integration-what-changes-when-you-build-accounting-apis-3doa</link>
      <guid>https://dev.to/apideck/multi-entity-general-ledger-integration-what-changes-when-you-build-accounting-apis-3doa</guid>
      <description>&lt;p&gt;Most &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integrations&lt;/a&gt; are built for a single company with a single set of books. You read invoices, write journal entries, sync payments. The chart of accounts has a few dozen line items. Everything maps cleanly to one general ledger.&lt;/p&gt;

&lt;p&gt;Then your customer has two subsidiaries. Or five. Or forty, spread across three currencies and two continents. Suddenly you're dealing with subsidiary hierarchies, intercompany eliminations, and consolidation logic that varies by platform. The GL integration you built for a single-entity QuickBooks user doesn't translate to a &lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; OneWorld customer running twelve legal entities.&lt;/p&gt;

&lt;p&gt;This is the part of &lt;a href="https://www.apideck.com/blog/accounting-software-integration" rel="noopener noreferrer"&gt;accounting software integration&lt;/a&gt; that most teams underestimate. And it's the part that matters most for the customers with real money on the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  How multi-entity accounting changes the general ledger
&lt;/h2&gt;

&lt;p&gt;A multi-entity setup means a business operates multiple legal entities that each maintain their own financial records but ultimately roll up into a consolidated view. These entities might be subsidiaries in different countries, regional branches, or distinct business divisions. Each entity has its own general ledger. Each general ledger is organized around a chart of accounts. In simple setups, all entities share the same chart of accounts. In more complex ones, entities have their own accounts tailored to local tax or regulatory requirements, with a mapping layer that connects them for consolidation.&lt;/p&gt;

&lt;p&gt;The general ledger is the backbone. Every transaction in an accounting system, whether it originates from AP, AR, or any other module, ultimately generates journal entries with offsetting debits and credits in the GL. Approve a vendor bill and the system records an expense and a liability. Process the payment and it reduces both the liability and cash. In a well-integrated ERP, none of this requires manual posting.&lt;/p&gt;

&lt;p&gt;Multi-entity complicates things in ways that compound quickly.&lt;/p&gt;

&lt;p&gt;The most obvious challenge is intercompany transactions. When subsidiary A sells goods to subsidiary B, both entities record the transaction in their own books. But from a consolidated perspective, that revenue and cost need to be eliminated so the group financials reflect only external activity. If subsidiary A lent $500,000 to subsidiary B, the receivable on one side and the payable on the other need to cancel out during consolidation. Miss these eliminations and you inflate your consolidated numbers.&lt;/p&gt;

&lt;p&gt;Currency translation adds another layer. A subsidiary operating in EUR while the parent reports in USD needs its financials translated at appropriate exchange rates: current rates for monetary items, average rates for income statement items, historical rates for equity. Unrealized gains and losses from open foreign-currency positions need revaluation journal entries at each reporting period.&lt;/p&gt;

&lt;p&gt;Then there's dimensional reporting. Beyond the basic account structure, enterprises need to slice financial data by department, location, project, or other custom segments. Each accounting platform handles these "&lt;a href="https://www.apideck.com/blog/tracking-dimensions-in-accounting-integrations" rel="noopener noreferrer"&gt;tracking categories&lt;/a&gt;" differently, and the way they interact with entity structures varies considerably.&lt;/p&gt;

&lt;p&gt;Finance teams at multi-entity companies spend an average of six days per month manually consolidating financial data across entities, according to &lt;a href="https://softledger.com/" rel="noopener noreferrer"&gt;SoftLedger&lt;/a&gt;. NetSuite OneWorld carries a &lt;a href="https://www.brokenrubik.com/blog/netsuite-oneworld-guide" rel="noopener noreferrer"&gt;$10,000 to $30,000 annual premium&lt;/a&gt; over standard NetSuite specifically because multi-subsidiary management is that complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  How NetSuite, QuickBooks, Xero, and Sage Intacct model entities
&lt;/h2&gt;

&lt;p&gt;This is where it gets painful for anyone building integrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; OneWorld treats each legal entity as a &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N268563.html" rel="noopener noreferrer"&gt;subsidiary record in a hierarchical tree&lt;/a&gt;. All subsidiaries can share a chart of accounts (with optional country-specific accounts), and the system supports up to 250 subsidiaries per account. Intercompany journal entries are a &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N1475891.html" rel="noopener noreferrer"&gt;distinct transaction type&lt;/a&gt;. NetSuite automatically generates elimination entries during the period close process through its Automated Intercompany Management feature, posting them to dedicated "elimination subsidiaries" that exist solely for consolidation. Advanced Intercompany Journal Entries allow debits and credits across multiple subsidiaries in a single balanced entry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, by contrast, was built for single-entity accounting. Multi-entity businesses running QuickBooks typically operate separate company files for each entity. There's no native subsidiary hierarchy, no built-in intercompany elimination, no consolidated reporting. A biotech firm called Aviva Biology &lt;a href="https://www.houseblend.io/articles/netsuite-quickbooks-erp-evaluation" rel="noopener noreferrer"&gt;found that QuickBooks couldn't meet its multi-subsidiary needs&lt;/a&gt; after an acquisition, and switched to NetSuite OneWorld to consolidate finances across entities. This is a common migration pattern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt; supports multiple organizations under a single user login, but each organization is an isolated instance with its own chart of accounts and data. Consolidation happens outside the system, either through reporting tools like Fathom or through manual spreadsheet work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/sage-intacct" rel="noopener noreferrer"&gt;Sage Intacct&lt;/a&gt; takes a dimensional approach. Rather than rigid subsidiary hierarchies, it uses entities as one of several dimensions (alongside departments and locations) that can be applied to transactions and used for reporting. It supports multiple books per entity for parallel reporting under different accounting standards.&lt;/p&gt;

&lt;p&gt;The structural differences run deep. NetSuite has a dedicated subsidiary object type with a parent-child hierarchy. QuickBooks has no entity concept at all in its API. Xero and Sage Intacct each take their own approach: Xero with isolated organizations, Sage with entities as a reporting dimension. These are not cosmetic differences in naming. They represent different data models with different assumptions about how financial information is organized.&lt;/p&gt;

&lt;h2&gt;
  
  
  How multi-entity affects accounting API integrations
&lt;/h2&gt;

&lt;p&gt;When you're building a product that needs to read from or write to your customers' general ledgers, multi-entity support changes the integration surface area dramatically.&lt;/p&gt;

&lt;p&gt;Consider journal entries. In a single-entity QuickBooks integration, you POST a journal entry with line items that include account references and amounts. The QuickBooks API call looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;/company/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/journalentry&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"DetailType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JournalEntryLineDetail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"JournalEntryLineDetail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"PostingType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AccountRef"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"36"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checking"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"DetailType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JournalEntryLineDetail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"JournalEntryLineDetail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"PostingType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Credit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AccountRef"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"96"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Consulting Revenue"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Straightforward. One company, one set of accounts, one journal entry.&lt;/p&gt;

&lt;p&gt;In NetSuite OneWorld, that same journal entry needs to be scoped to a specific subsidiary. If it's an intercompany transaction, you need to use the Advanced Intercompany Journal Entry, specifying both the originating subsidiary and the receiving subsidiary. The payload gets more complex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Simplified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;illustration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/services/rest/record/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/advInterCompanyJournalEntry&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subsidiary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toSubsidiary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"210"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"debit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"subsidiary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"eliminate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"315"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"credit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"subsidiary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"eliminate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the differences: subsidiary scoping on the transaction and on each line item, a &lt;code&gt;toSubsidiary&lt;/code&gt; field, and an &lt;code&gt;eliminate&lt;/code&gt; flag that tells NetSuite to reverse these entries during consolidation. Your integration code needs to know whether the customer is on standard NetSuite or OneWorld, whether the journal crosses entity boundaries, and which clearing accounts the system expects.&lt;/p&gt;

&lt;p&gt;Consider the chart of accounts. In QuickBooks, you call the Account endpoint and get a flat list. In NetSuite, accounts exist in a hierarchy and may be shared across subsidiaries or scoped to specific ones. Sage Intacct combines accounts with dimensional attributes like department and location that determine how transactions are categorized for reporting. Each platform's version of what Xero calls "tracking categories" has a different name and a different data shape, and your integration has to normalize these into a consistent structure or handle each one separately. (For a deeper look at how tracking dimensions vary across platforms, see our &lt;a href="https://www.apideck.com/blog/tracking-dimensions-in-accounting-integrations" rel="noopener noreferrer"&gt;guide to tracking dimensions in accounting integrations&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Consider reporting. A trial balance in a single-entity system is one set of account balances. In a multi-entity system, you need trial balances per entity, a consolidated trial balance with intercompany eliminations applied, and potentially a currency-translated view. The API surface for these reports differs across every platform.&lt;/p&gt;

&lt;p&gt;Each integration ends up being its own project. QuickBooks is one data model. Xero is another. NetSuite is a third, substantially more complex model with subsidiary scoping on nearly every API call. Building and maintaining these individually means engineering teams spend a disproportionate amount of time on connector logic rather than core product work. Individual accounting integrations typically cost &lt;a href="https://www.apideck.com/blog/accounting-software-integration" rel="noopener noreferrer"&gt;$3,000 to $15,000&lt;/a&gt; each when you factor in engineering time, infrastructure, and the normalization work required to make financial data consistent across sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a unified accounting API for multi-entity integrations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;Unified accounting APIs&lt;/a&gt; attempt to solve this by providing a single normalized interface across multiple platforms. You make one API call for journal entries, one for chart of accounts, one for invoices, and the unified layer translates your request into the native format of whatever platform your customer uses.&lt;/p&gt;

&lt;p&gt;This works well for the common case. Standard transactional objects like invoices and payments follow broadly similar patterns across platforms (though the field names and nesting structures differ). A unified API normalizes these into a consistent schema and manages the ongoing maintenance when providers ship breaking changes. It also handles authentication, which varies widely: QuickBooks uses OAuth 2.0 with strict refresh token rotation. NetSuite requires token-based authentication with HMAC-SHA256 signature generation on every request.&lt;/p&gt;

&lt;p&gt;Here's what the same journal entry looks like through the &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck Accounting API&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;journalEntriesAdd&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;journalEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Intercompany transfer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;210&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tracking_categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subsidiary-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ledger_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;315&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tracking_categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subsidiary-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call, one data model. The unified layer resolves which downstream platform the customer uses and translates the request into the native format, whether that's QuickBooks, NetSuite, Xero, or &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;any of the 30+ supported connectors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For multi-entity scenarios, the unified layer needs to do more. It needs to surface entity and subsidiary context on relevant endpoints. It needs to handle tracking categories and dimensional reporting consistently, even when each platform implements them differently. It needs to support journal entries that span entities where the underlying platform allows it, and degrade gracefully where it doesn't.&lt;/p&gt;

&lt;p&gt;The Apideck Accounting API handles this through a &lt;a href="https://www.apideck.com/blog/tracking-dimensions-in-accounting-integrations" rel="noopener noreferrer"&gt;unified tracking categories endpoint&lt;/a&gt; that abstracts the differences between each platform's implementation into a single interface. Rather than learning four different tracking systems with four different names and four different data shapes, developers use one API that works identically across all supported platforms. For subsidiary and entity context, the unified model includes company and organization-level scoping that maps to the native entity model of each downstream provider.&lt;/p&gt;

&lt;p&gt;This doesn't eliminate all complexity. A deep NetSuite OneWorld integration with custom intercompany allocation schedules will always require some platform-specific configuration. But for the 80% of use cases where you need to move GL data and transactional records across multiple entities and multiple platforms, the unified approach compresses months of integration work into weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next for multi-entity accounting APIs
&lt;/h2&gt;

&lt;p&gt;The multi-entity problem is growing, not shrinking. Cross-border commerce is expanding. Vertical SaaS platforms serving franchise operators and multi-location businesses need to connect with the accounting systems of customers who may operate dozens of entities. Fintech products offering &lt;a href="https://www.apideck.com/blog/accounting-erp-integration-banks-use-cases" rel="noopener noreferrer"&gt;embedded lending&lt;/a&gt; or working capital need reliable consolidated financial data to underwrite risk accurately.&lt;/p&gt;

&lt;p&gt;At the same time, &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;accounting platforms&lt;/a&gt; are investing heavily in their multi-entity capabilities. Intuit launched its Enterprise Suite with multi-entity features aimed at mid-market companies. New entrants like Campfire are building AI-native ERPs with multi-entity consolidation as a first-class feature. NetSuite continues expanding OneWorld's automated intercompany management capabilities.&lt;/p&gt;

&lt;p&gt;For integration builders, this means the bar keeps rising. It's no longer sufficient to connect to a single QuickBooks company and call it done. Customers expect their software to understand that they run multiple entities, that those entities have relationships with each other, and that the financial data flowing through the integration reflects the consolidated reality of their business. The GL is not just a list of accounts. It's a hierarchical, multi-dimensional, entity-scoped structure that changes shape depending on which accounting platform you're looking at. Building integrations that handle this well is hard. Using a &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;unified API&lt;/a&gt; that has already mapped these differences is the faster path to getting it right.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is multi-entity accounting?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multi-entity accounting is the practice of maintaining separate financial records for multiple legal entities (subsidiaries, branches, or divisions) within a single organization, while consolidating them into unified financial statements. Each entity has its own general ledger and chart of accounts, and the consolidation process requires eliminating intercompany transactions and translating foreign currencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do accounting APIs handle subsidiaries?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each accounting platform handles subsidiaries differently at the API level. NetSuite OneWorld requires subsidiary scoping on most API calls and has dedicated intercompany journal entry endpoints. QuickBooks has no subsidiary concept in its API, so multi-entity businesses use separate company files. Xero treats each entity as an isolated organization. A unified accounting API normalizes these differences into a single data model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are intercompany eliminations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Intercompany eliminations are accounting adjustments that remove the effect of transactions between entities within the same corporate group during financial consolidation. If subsidiary A sold $100,000 of services to subsidiary B, that revenue and expense need to be eliminated so consolidated financials only reflect transactions with external parties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a unified accounting API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A unified accounting API provides a single, normalized interface for integrating with multiple accounting platforms. Instead of building separate connectors for QuickBooks, Xero, NetSuite, and Sage Intacct, developers build once against the unified API, which translates requests into each platform's native format and handles authentication, pagination, and data normalization.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>fintech</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>SAP Business One API Integration Guide: Authentication, Endpoints, and OData Queries</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 10 Jul 2026 09:51:13 +0000</pubDate>
      <link>https://dev.to/apideck/sap-business-one-api-integration-guide-authentication-endpoints-and-odata-queries-ean</link>
      <guid>https://dev.to/apideck/sap-business-one-api-integration-guide-authentication-endpoints-and-odata-queries-ean</guid>
      <description>&lt;p&gt;SAP Business One runs the back offices of over 80,000 small and mid-sized companies worldwide. If you're building software that needs to read invoices, sync customers, or push journal entries into any of those systems, you'll eventually find yourself working with the Service Layer API. It's SAP's REST interface for Business One, and it's both more capable and more quirky than you might expect coming from modern SaaS APIs.&lt;/p&gt;

&lt;p&gt;This is a practical walkthrough of what the integration actually looks like: authentication, core endpoints, querying patterns, pagination, and the gotchas that will cost you hours if nobody warns you about them first. If you're looking for SAP's enterprise product instead, see our &lt;a href="https://www.apideck.com/blog/guide-to-sap-4-hana-rest-and-soap-api" rel="noopener noreferrer"&gt;SAP S/4HANA API integration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the SAP Business One Service Layer?
&lt;/h2&gt;

&lt;p&gt;The Service Layer is SAP Business One's OData-based REST API. It sits on top of an Apache HTTP server and talks to the same business object layer that the desktop client uses (the DI Core). That last part matters, because it means the API enforces the same validation rules and business logic as the application itself. You can't create an invoice with a missing required field through the API any more than an accountant could through the UI.&lt;/p&gt;

&lt;p&gt;As of Feature Pack 2405, SAP has deprecated OData v3 and made OData v4 the primary protocol. The practical difference: your base URL path changes from &lt;code&gt;/b1s/v1/&lt;/code&gt; to &lt;code&gt;/b1s/v2/&lt;/code&gt;, and the JSON response format follows OData v4 conventions (annotations use the &lt;code&gt;@odata.&lt;/code&gt; prefix instead of &lt;code&gt;odata.&lt;/code&gt;). If you're starting a new integration today, use v2.&lt;/p&gt;

&lt;p&gt;Your Service Layer instance runs at a URL like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://your-sap-server:50000/b1s/v2/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The port and hostname depend on the customer's deployment. SAP Business One can run on either Microsoft SQL Server or SAP HANA, and the Service Layer works the same way on both. SAP publishes a complete &lt;a href="https://help.sap.com/doc/056f69366b5345a386bb8149f1700c19/10.0/en-US/Service%20Layer%20API%20Reference.html" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; covering every entity and action, along with a detailed &lt;a href="https://help.sap.com/doc/fc2f5477516c404c8bf9ad1315a17238/10.0/en-US/Working_with_SAP_Business_One_Service_Layer.pdf" rel="noopener noreferrer"&gt;Service Layer user manual&lt;/a&gt; that covers installation, configuration, and query patterns.&lt;/p&gt;

&lt;p&gt;It's worth knowing that the Service Layer is not SAP Business One's only integration path. The DI API is a .NET library for deep server-side access, mainly used by desktop add-ons running on the same machine as the SAP client. The &lt;a href="https://help.sap.com/docs/INTEGRATION_FRAMEWORK_FOR_SAP_BUSINESS_ONE" rel="noopener noreferrer"&gt;Integration Framework (B1If)&lt;/a&gt; is a Java-based middleware layer that lets SAP partners build custom passive REST endpoints and event-driven workflows using XSLT transformations. B1If routes through the DI API on SQL Server deployments and through the Service Layer on HANA. Some partners, like Brazilian consultancy &lt;a href="https://blogs.sap.com/2020/05/04/rest-api-for-sap-business-one-using-sap-b1-integration-framework-b1if/" rel="noopener noreferrer"&gt;Ogeda &amp;amp; Nietsche&lt;/a&gt;, have used B1If to build domain-specific REST APIs (ecommerce, CRM, WMS) that wrap SAP Business One objects in a loosely coupled integration layer. The Service Layer is the right starting point for most external integrations: it's REST-native, requires no middleware installation, and works identically on both database backends.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to authenticate with the SAP Business One API
&lt;/h2&gt;

&lt;p&gt;This is where the Service Layer diverges from what you're used to with modern APIs. There are no API keys. No persistent bearer tokens. You authenticate by posting credentials and getting a session cookie back.&lt;/p&gt;

&lt;p&gt;The login request looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://your-sap-server:50000/b1s/v2/Login

{
  "CompanyDB": "SBODemoUS",
  "UserName": "manager",
  "Password": "your-password"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the credentials are valid, you get back a 200 response with a &lt;code&gt;SessionId&lt;/code&gt; in the body and a &lt;code&gt;B1SESSION&lt;/code&gt; cookie in the response headers. Every subsequent request must include that cookie.&lt;/p&gt;

&lt;p&gt;In Python, a basic connection using &lt;code&gt;requests&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-sap-server:50000/b1s/v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# only for self-signed certs in dev
&lt;/span&gt;
&lt;span class="c1"&gt;# Authenticate
&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/Login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CompanyDB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SBODemoUS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UserName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Query customers
&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/BusinessPartners&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CardType eq &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cCustomer&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$select&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CardCode,CardName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$top&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CardCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CardName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Logout when done
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/Logout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail: use a &lt;code&gt;requests.Session()&lt;/code&gt; object. It automatically persists the &lt;code&gt;B1SESSION&lt;/code&gt; cookie across requests, so you authenticate once and every subsequent call reuses the same session. If you create a new &lt;code&gt;requests.post()&lt;/code&gt; for each call without a shared session, you'll re-authenticate every time and hit the 5-second overhead on each request.&lt;/p&gt;

&lt;p&gt;The session expires after 30 minutes of inactivity by default. You can extend this in the Service Layer configuration (the &lt;code&gt;b1s.conf&lt;/code&gt; file) or in the SLD Control Center, but most integrations handle it by catching the 401 response and re-authenticating automatically.&lt;/p&gt;

&lt;p&gt;One detail that catches people: the first authenticated request to the Service Layer can take around 5 seconds. Subsequent requests using the session cookie return in roughly 20 milliseconds. This is a well-known behavior in the SAP community, and it means you absolutely need to reuse sessions rather than authenticating per request. If you're running a web application that calls the Service Layer from a backend, implement a session cache that stores the &lt;code&gt;B1SESSION&lt;/code&gt; cookie and only re-authenticates when the session expires.&lt;/p&gt;

&lt;p&gt;SAP also added OAuth 2.0 and OpenID Connect support starting in version 10.0 FP 2305. This requires configuring an identity provider (SAP IAS or another SAML2-compatible IDP) and is more complex to set up, but it's worth considering if your integration needs to support SSO or if you're building a multi-tenant application where managing per-company credentials gets unwieldy.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAP Business One API endpoints for accounting data
&lt;/h2&gt;

&lt;p&gt;The API exposes SAP Business One's business objects as OData entities. The ones you'll work with most often in an &lt;a href="https://www.apideck.com/blog/accounting-software-integration" rel="noopener noreferrer"&gt;accounting software integration&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BusinessPartners&lt;/code&gt; for customers and vendors. &lt;code&gt;Items&lt;/code&gt; for products and services. &lt;code&gt;Orders&lt;/code&gt; for sales orders. &lt;code&gt;Invoices&lt;/code&gt; for A/R invoices. &lt;code&gt;PurchaseInvoices&lt;/code&gt; for A/P invoices. &lt;code&gt;JournalEntries&lt;/code&gt; for general ledger postings. &lt;code&gt;ChartOfAccounts&lt;/code&gt; for the GL account list.&lt;/p&gt;

&lt;p&gt;All of these support standard HTTP methods. A GET to retrieve, POST to create, PATCH to update, DELETE to remove. Here's what creating an invoice looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /b1s/v2/Invoices

{
  "CardCode": "C20000",
  "DocDate": "2026-03-15",
  "DocumentLines": [
    {
      "ItemCode": "A00001",
      "Quantity": 10,
      "UnitPrice": 25.00
    }
  ]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CardCode&lt;/code&gt; references an existing BusinessPartner. The API validates that the partner exists, that the item code is valid, and that all required fields are present before writing anything to the database. If validation fails, you get a structured error response with the SAP error code and message.&lt;/p&gt;

&lt;h2&gt;
  
  
  OData query options: filtering, sorting, and joins
&lt;/h2&gt;

&lt;p&gt;OData gives you a consistent query language across all entities. The basics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/BusinessPartners?$select=CardCode,CardName&amp;amp;$filter=CardType eq 'cCustomer'&amp;amp;$orderby=CardCode&amp;amp;$top=50
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the first 50 customers, selecting only the fields you need. The filter operators include &lt;code&gt;eq&lt;/code&gt;, &lt;code&gt;ne&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;ge&lt;/code&gt;, &lt;code&gt;le&lt;/code&gt;, &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, plus string functions like &lt;code&gt;startswith()&lt;/code&gt; and &lt;code&gt;contains()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can navigate relationships using OData associations. If you have an order's &lt;code&gt;DocEntry&lt;/code&gt;, you can retrieve the associated customer in a single request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/Orders(7)/BusinessPartner
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aggregation support was added for HANA deployments and uses the &lt;code&gt;$apply&lt;/code&gt; query option. You can run sums, averages, counts, and group-by operations directly through the API, which can save you from pulling large datasets just to aggregate on the client side.&lt;/p&gt;

&lt;p&gt;The biggest limitation: you cannot join across entities in a single query. Each API call returns data from one endpoint. If you need to correlate invoices with their associated payments, that's two separate requests and a client-side join. For complex reporting queries, SAP requires you to save the query in Query Manager first, then call it through the &lt;code&gt;SQLQueries&lt;/code&gt; endpoint. You can't pass arbitrary SQL through the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination and page size configuration
&lt;/h2&gt;

&lt;p&gt;The default page size is 20 records. If you request &lt;code&gt;/b1s/v2/Invoices&lt;/code&gt;, you'll get back 20 invoices and an &lt;code&gt;@odata.nextLink&lt;/code&gt; annotation pointing to the next page.&lt;/p&gt;

&lt;p&gt;You have two options for controlling this. Client-driven paging uses &lt;code&gt;$top&lt;/code&gt; and &lt;code&gt;$skip&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/Invoices?$top=100&amp;amp;$skip=200
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server-driven paging uses the &lt;code&gt;Prefer&lt;/code&gt; header to request a larger page size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/Invoices
Prefer: odata.maxpagesize=100
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server responds with a &lt;code&gt;Preference-Applied&lt;/code&gt; header confirming the page size. You can also set &lt;code&gt;PageSize&lt;/code&gt; globally in the &lt;code&gt;b1s.conf&lt;/code&gt; configuration file, though this requires Service Layer restart and affects all clients.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;odata.maxpagesize=0&lt;/code&gt; disables pagination entirely. This is useful for small reference tables like chart of accounts but dangerous for transactional entities with thousands of records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch operations and atomic transactions
&lt;/h2&gt;

&lt;p&gt;The Service Layer supports OData batch requests through the &lt;code&gt;/$batch&lt;/code&gt; endpoint. This lets you bundle multiple operations into a single HTTP request, and operations within a changeset are atomic: if one fails, they all roll back.&lt;/p&gt;

&lt;p&gt;This is useful for scenarios like creating an invoice and its payment in a single atomic operation, or bulk-updating inventory across multiple items. The request format uses multipart MIME boundaries, which is ugly to construct manually but well-supported by most OData client libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  User-Defined Fields and User-Defined Objects
&lt;/h2&gt;

&lt;p&gt;Almost every SAP Business One customer has customized their instance with User-Defined Fields (UDFs) and User-Defined Tables (UDTs). If your integration ignores these, you'll lose data on every sync.&lt;/p&gt;

&lt;p&gt;UDFs are accessible through the Service Layer on the parent entity. A custom field added to BusinessPartners named "CustomerTier" appears in the API response as &lt;code&gt;U_CustomerTier&lt;/code&gt; (all UDFs carry the &lt;code&gt;U_&lt;/code&gt; prefix). You can read, write, and filter on UDFs the same way you would any standard property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/BusinessPartners?$filter=U_CustomerTier eq 'Gold'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User-Defined Tables work similarly but use the &lt;code&gt;U_&lt;/code&gt; prefix on the table name. You access them through a dedicated endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /b1s/v2/U_CUSTOMTABLE
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User-Defined Objects (UDOs) go a step further, wrapping UDTs with business logic like find, add, update, and delete operations. The Service Layer exposes registered UDOs as first-class entities, so they behave like any built-in object.&lt;/p&gt;

&lt;p&gt;The catch: UDFs, UDTs, and UDOs vary from customer to customer. Your integration needs to discover them at runtime using the &lt;code&gt;/$metadata&lt;/code&gt; endpoint or the &lt;code&gt;UserFieldsMD&lt;/code&gt; and &lt;code&gt;UserTablesMD&lt;/code&gt; entities, rather than assuming a fixed schema. This is one of the more time-consuming parts of building a production SAP Business One integration, and it's a common reason teams choose a &lt;a href="https://www.apideck.com/blog/from-accounts-receivable-to-lending-automation-integration-use-cases-for-vertical-saas" rel="noopener noreferrer"&gt;unified API approach&lt;/a&gt; that normalizes these differences across customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling and debugging
&lt;/h2&gt;

&lt;p&gt;The Service Layer returns errors as JSON objects with an &lt;code&gt;error&lt;/code&gt; key containing a &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt;. The codes come from SAP's DI API layer and carry more diagnostic value than the HTTP status alone. A 400 response might mean a missing required field, a referential integrity violation, or a business logic constraint like trying to post to a closed accounting period.&lt;/p&gt;

&lt;p&gt;Some common error patterns worth handling explicitly:&lt;/p&gt;

&lt;p&gt;Error code &lt;code&gt;-5002&lt;/code&gt; means the session has expired. Catch this and re-authenticate rather than failing the entire sync. Error code &lt;code&gt;-2028&lt;/code&gt; indicates a locked object, which happens when another user or process has the same record open. The right response is to retry with exponential backoff. Error code &lt;code&gt;-10&lt;/code&gt; is a generic validation failure; the &lt;code&gt;message.value&lt;/code&gt; field will contain the specific field and constraint that failed.&lt;/p&gt;

&lt;p&gt;Log the full error response body on every failure. SAP support and implementation partners will ask for the error code, the request payload, and the entity path. Without these, troubleshooting becomes guesswork.&lt;/p&gt;

&lt;p&gt;For debugging during development, the Service Layer's &lt;code&gt;/$metadata&lt;/code&gt; endpoint is indispensable. It returns the full OData schema for every entity, property, and association available in the API. Generate your client code from this schema rather than hard-coding property names, because SAP adds and occasionally renames properties across feature packs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production deployment considerations
&lt;/h2&gt;

&lt;p&gt;Session management is the single most important thing to get right. Build a session pool that re-authenticates lazily when the 30-minute timeout hits, and never authenticate per-request. The 5-second authentication overhead will destroy your throughput otherwise.&lt;/p&gt;

&lt;p&gt;Handle the &lt;code&gt;CompanyDB&lt;/code&gt; parameter carefully. SAP Business One customers often run multiple company databases on the same server. Your session is scoped to a single company database, so if you need to sync data across companies, you need separate sessions for each.&lt;/p&gt;

&lt;p&gt;The Service Layer enforces TLS 1.2 or 1.3 and requires a valid X.509 certificate. In practice, many on-premise deployments use self-signed certificates, which means your HTTP client needs to be configured to trust that specific certificate rather than skipping validation entirely.&lt;/p&gt;

&lt;p&gt;Because SAP Business One is typically deployed on-premise, your integration also needs to account for network connectivity. Unlike cloud APIs where you can call an endpoint from anywhere, reaching a customer's Service Layer often requires VPN access, IP allowlisting, or a reverse proxy. Some SAP partners deploy a proxy layer in front of the Service Layer to handle session caching and &lt;a href="https://infisical.com/blog/what-is-certificate-manager" rel="noopener noreferrer"&gt;certificate management&lt;/a&gt; for external clients. This on-premise reality is one of the main reasons &lt;a href="https://www.apideck.com/blog/accounting-erp-integration-banks-use-cases" rel="noopener noreferrer"&gt;ERP and accounting integrations&lt;/a&gt; are more complex than connecting to a cloud-native API like Xero or QuickBooks Online.&lt;/p&gt;

&lt;p&gt;The Service Layer isn't the most elegant API you'll ever work with. The session-based auth, the OData conventions, and the on-premise deployment model all add friction compared to a typical cloud API. But it's well-documented, it's functionally complete, and it exposes the full depth of SAP Business One's business logic. For any SaaS company that needs to support SAP Business One customers, understanding the Service Layer is a prerequisite, not an option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What authentication methods does the SAP Business One Service Layer support?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Service Layer supports two authentication schemes. Basic authentication uses a username, password, and CompanyDB posted to the &lt;code&gt;/Login&lt;/code&gt; endpoint, returning a &lt;code&gt;B1SESSION&lt;/code&gt; cookie valid for 30 minutes. OAuth 2.0 with OpenID Connect is available from version 10.0 FP 2305 onward and requires an identity provider like SAP IAS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the default session timeout for SAP Business One Service Layer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The default session timeout is 30 minutes of inactivity. You can extend this in the &lt;code&gt;b1s.conf&lt;/code&gt; configuration file or through the SLD Control Center. Most production integrations handle expiry by catching the &lt;code&gt;-5002&lt;/code&gt; error code and re-authenticating automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you use OData v3 and v4 with SAP Business One Service Layer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As of Feature Pack 2405, OData v3 is deprecated and OData v4 is the primary protocol. OData v3 still works for backward compatibility (use the &lt;code&gt;/b1s/v1/&lt;/code&gt; path), but SAP recommends migrating to v4 (&lt;code&gt;/b1s/v2/&lt;/code&gt;) for new integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the default page size for SAP Business One API responses?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The default page size is 20 records. You can change this per-request using the &lt;code&gt;Prefer: odata.maxpagesize=N&lt;/code&gt; header, or globally by setting &lt;code&gt;PageSize&lt;/code&gt; in the &lt;code&gt;b1s.conf&lt;/code&gt; file. Setting the page size to 0 disables pagination, which is useful for small lookup tables but risky for large transactional datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you handle User-Defined Fields in the Service Layer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UDFs appear on their parent entity with a &lt;code&gt;U_&lt;/code&gt; prefix. A custom field called "Region" on BusinessPartners becomes &lt;code&gt;U_Region&lt;/code&gt; in the API. You can read, write, and filter on UDFs like any standard field. Discover available UDFs at runtime through the &lt;code&gt;UserFieldsMD&lt;/code&gt; entity or the &lt;code&gt;/$metadata&lt;/code&gt; endpoint.&lt;/p&gt;

</description>
      <category>api</category>
      <category>sapbusinessone</category>
      <category>erp</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>How to Integrate With the Campfire API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:03:22 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-campfire-api-gjo</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-campfire-api-gjo</guid>
      <description>&lt;p&gt;Campfire is an AI-native ERP platform built for high-growth startups and mid-market companies that are outgrowing QuickBooks or Xero, or looking for a modern alternative to NetSuite and Sage Intacct. Founded in 2023 and backed by Accel, Ribbit, Foundation Capital, Y Combinator, and Capital49, it offers a general ledger with multi-entity and multi-currency support, revenue recognition, invoicing, close management, and bank reconciliation. The company raised a $35M Series A led by Accel in June 2025, followed by a $65M Series B co-led by Accel and Ribbit just 12 weeks later, bringing total funding past $100M.&lt;/p&gt;

&lt;p&gt;If you're building a product that needs to sync accounting data with Campfire, this guide covers two approaches: integrating directly with the Campfire API, and using a unified API like Apideck to build an &lt;a href="https://www.apideck.com/blog/how-to-integrate-with-the-campfire-api" rel="noopener noreferrer"&gt;Campfire Integration&lt;/a&gt; (and 40+ other accounting platforms).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Campfire API covers
&lt;/h2&gt;

&lt;p&gt;Campfire exposes a REST API organized around its core accounting workflows. The base URL for all requests is &lt;code&gt;https://api.meetcampfire.com&lt;/code&gt;, and the full API reference lives at &lt;a href="https://docs.campfire.ai" rel="noopener noreferrer"&gt;docs.campfire.ai&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The API is split across several resource groups:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cash management&lt;/strong&gt; covers bank accounts and bank transactions. You can list, create, update, and delete bank accounts, manage individual bank transactions with full CRUD support, and push bank feed data for reconciliation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts payable&lt;/strong&gt; handles bills, debit memos, and payment workflows. Beyond basic CRUD, it includes endpoints for marking bills as paid, voiding bills, reopening voided bills, and bulk searching bills by number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts receivable&lt;/strong&gt; includes invoices, credit memos, and payment recording. It supports bulk invoice creation, bulk search by invoice number, voiding, and reopening voided invoices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core accounting&lt;/strong&gt; covers journal entries (single-entity and intercompany), budgets, fixed assets, chart transactions, vendor contacts, and custom dimensions. It also includes bulk search endpoints for departments, vendors, and custom dimensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Financial statements&lt;/strong&gt; gives programmatic access to income statements, balance sheets, cash flow statements, trial balances, general ledger data, comparative statements, and cash-basis variants of income and operating statements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue recognition&lt;/strong&gt; handles contracts, subscriptions, milestones, usage-based revenue, products, bundles, and bundle allocations. You can create, modify, duplicate, and terminate contracts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Company objects&lt;/strong&gt; include chart of accounts, departments, vendors, custom fields, custom dimensions (and dimension groups), cost allocations, and fixed asset classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Settings&lt;/strong&gt; cover entity management, file uploads, currency configuration, and exchange rates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrations&lt;/strong&gt; provide webhook management for event-driven sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bank reconciliation&lt;/strong&gt; includes reconciliation report management, GL transaction listing for reconciliation, and bulk matching of GL transactions to statement transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Campfire uses static API key authentication. To get set up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into your Campfire instance and go to &lt;strong&gt;Settings &amp;gt; API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click "Create User" and assign a role. The three roles determine access scope:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt; grants access to all API endpoints&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clerk&lt;/code&gt; allows viewing all data and posting draft entries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;view only&lt;/code&gt; restricts to GET endpoints&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click "Create API Key" on the user. Copy the key immediately since it's only shown once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Include the token in every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Token YOUR_API_KEY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no OAuth flow for the API itself (though Campfire does support MCP-based integration for AI tools, covered later).&lt;/p&gt;

&lt;h2&gt;
  
  
  API conventions
&lt;/h2&gt;

&lt;p&gt;Before jumping into examples, a few things about how the API behaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endpoint paths vary by resource group.&lt;/strong&gt; Bank accounts live under &lt;code&gt;/ca/api/account&lt;/code&gt;, bills under &lt;code&gt;/ca/api/v1/bill&lt;/code&gt;, invoices under &lt;code&gt;/ca/api/v1/invoice&lt;/code&gt;, journal entries under &lt;code&gt;/ca/api/journal&lt;/code&gt;, and webhooks under &lt;code&gt;/integrations/api/v1/webhook&lt;/code&gt;. There isn't a single prefix convention, so check the API reference for each resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination&lt;/strong&gt; uses &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt; query parameters. Paginated responses return a &lt;code&gt;count&lt;/code&gt;, a &lt;code&gt;results&lt;/code&gt; array, and &lt;code&gt;next&lt;/code&gt;/&lt;code&gt;previous&lt;/code&gt; URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.meetcampfire.com/ca/api/account?offset=100&amp;amp;limit=100"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previous"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Incremental sync&lt;/strong&gt; is supported through &lt;code&gt;last_modified_at__gte&lt;/code&gt; and &lt;code&gt;last_modified_at__lte&lt;/code&gt; filters (ISO 8601 format). Most list endpoints also support an &lt;code&gt;include_deleted&lt;/code&gt; parameter. When set to &lt;code&gt;true&lt;/code&gt;, it returns only deleted records (not active ones) with &lt;code&gt;is_deleted=true&lt;/code&gt; and a &lt;code&gt;deleted_at&lt;/code&gt; timestamp. When &lt;code&gt;false&lt;/code&gt; or omitted, you get only active records. This toggle-based design gives you clean separation between active and deleted data, which is useful for maintaining data consistency across systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object references&lt;/strong&gt; are numeric IDs. When creating bills, invoices, or journal entries, you first need to look up the IDs of related objects (entities, accounts, vendors, departments, custom dimensions) through their respective list endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-currency&lt;/strong&gt; is handled through a two-tier exchange rate model: &lt;code&gt;exchange_rate_book&lt;/code&gt; (between the transaction currency and the entity's book currency) and &lt;code&gt;exchange_rate&lt;/code&gt; (between the entity currency and the consolidation currency). Both can be auto-computed if omitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct integration examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Listing entities
&lt;/h3&gt;

&lt;p&gt;Multi-entity support is core to Campfire. You need entity IDs for nearly every write operation, so this is often the first call an integration makes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/entity &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes parent-child relationships, currencies, and exchange rate configuration per entity. There's also a dedicated endpoint for listing entities available for intercompany journal entries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing the chart of accounts
&lt;/h3&gt;

&lt;p&gt;Account IDs are required for bills, journal entries, and most write operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/v1/chart_accounts?limit=100'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can filter by account type and subtype to narrow results. There's also a separate endpoint for income statement accounts specifically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing bank accounts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/account &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports sorting by most returned fields (prefix with &lt;code&gt;-&lt;/code&gt; for descending, e.g. &lt;code&gt;?ordering=-current_balance&lt;/code&gt;), and filtering by &lt;code&gt;chart_of_accounts_account&lt;/code&gt; to find bank accounts linked to a specific GL account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a vendor
&lt;/h3&gt;

&lt;p&gt;Before you can create bills, you need vendor records. This is a common first step when syncing supplier data from an external system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/vendor &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "Acme Cloud Services",
    "email": "billing@acme.example.com",
    "currency": "USD",
    "payment_terms": "net_30"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the bulk search endpoint (&lt;code&gt;POST /ca/api/v1/vendor/bulk_search&lt;/code&gt;) to look up vendors by external ID or name, which is useful for matching records during initial sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a bill
&lt;/h3&gt;

&lt;p&gt;Bills require references to existing entities, vendors, and expense accounts. Fetch those first, then assemble the bill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/bill &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "terms": "net_30",
    "bill_date": "2025-07-01",
    "due_date": "2025-08-25",
    "lines": [
      {
        "account": "180564",
        "amount": 10000,
        "description": "Monthly SaaS subscription",
        "department": "35",
        "tags": [{"id": "8114"}]
      }
    ],
    "entity": "5",
    "currency": "USD",
    "vendor": "34182",
    "bill_number": "098765",
    "item_date": "2025-07-01",
    "mailing_address": "1234 Main St. San Francisco CA"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fields on bills: &lt;code&gt;terms&lt;/code&gt; accepts values like &lt;code&gt;net_7&lt;/code&gt;, &lt;code&gt;net_10&lt;/code&gt;, &lt;code&gt;net_15&lt;/code&gt;, &lt;code&gt;net_20&lt;/code&gt;, &lt;code&gt;net_30&lt;/code&gt;, &lt;code&gt;net_40&lt;/code&gt;, &lt;code&gt;net_45&lt;/code&gt;, &lt;code&gt;net_60&lt;/code&gt;, &lt;code&gt;net_90&lt;/code&gt;, &lt;code&gt;net_105&lt;/code&gt;, &lt;code&gt;net_120&lt;/code&gt;, or &lt;code&gt;due_on_receipt&lt;/code&gt;. Each line item references an &lt;code&gt;account&lt;/code&gt; (the expense account), and can optionally include a &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt; (custom dimension field IDs), and &lt;code&gt;bill_customer&lt;/code&gt; for cost center tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recording a bill payment
&lt;/h3&gt;

&lt;p&gt;Payments in Campfire work through a transaction-based model. You don't simply mark a bill as paid with an amount. You reference an existing journal entry transaction (either one you created or one you found via the list transactions endpoint) and apply it to the bill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/bill/&lt;span class="o"&gt;{&lt;/span&gt;bill_id&lt;span class="o"&gt;}&lt;/span&gt;/mark_as_paid &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "transactions": [
      {
        "transaction_id": 21069178,
        "account_id": "2550",
        "amount": 8999.99
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the payment doesn't use the full amount of the referenced transaction, Campfire creates a new journal line for the remainder. You can apply multiple transactions to a single bill in one call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an invoice
&lt;/h3&gt;

&lt;p&gt;Invoices use a different set of reference objects than bills. They require a &lt;code&gt;client&lt;/code&gt; (not &lt;code&gt;vendor&lt;/code&gt;), a &lt;code&gt;product&lt;/code&gt;, and an &lt;code&gt;entity&lt;/code&gt;. Optionally, you can link to a revenue recognition contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/invoice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "terms": "net_30",
    "invoice_date": "2025-07-01",
    "due_date": "2025-07-31",
    "lines": [
      {
        "service_date": "2025-07-01",
        "amount": 300,
        "product": "3560",
        "rate": 15,
        "quantity": 20,
        "description": "API usage - July 2025",
        "tax": 12
      }
    ],
    "entity": "54",
    "currency": "USD",
    "client": "24020",
    "invoice_number": "INV-2025-001",
    "billing_address": "456 Commerce Ave, New York NY",
    "period_start": "2025-07-01",
    "period_end": "2025-07-31",
    "contract": "24899"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invoice line items have &lt;code&gt;product&lt;/code&gt;, &lt;code&gt;rate&lt;/code&gt;, &lt;code&gt;quantity&lt;/code&gt;, and &lt;code&gt;tax&lt;/code&gt; fields, which differ from bill line items that use &lt;code&gt;account&lt;/code&gt; and a flat &lt;code&gt;amount&lt;/code&gt;. The &lt;code&gt;contract&lt;/code&gt; field ties the invoice to a revenue recognition contract for ASC 606 compliance.&lt;/p&gt;

&lt;p&gt;For high-volume scenarios, use the bulk create endpoint (&lt;code&gt;POST /ca/api/v1/invoice/bulk_create&lt;/code&gt;) to submit multiple invoices in a single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recording an invoice payment
&lt;/h3&gt;

&lt;p&gt;Invoice payments follow the same transaction-based pattern as bill payments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/invoice/&lt;span class="o"&gt;{&lt;/span&gt;invoice_id&lt;span class="o"&gt;}&lt;/span&gt;/mark_as_paid &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "transactions": [
      {
        "transaction_id": 21069200,
        "account_id": "2550",
        "amount": 300
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same mechanics as bill payments: reference an existing journal transaction, and Campfire handles the remainder if the payment is partial. You can also void an invoice payment without voiding the invoice itself using the &lt;code&gt;/void_payment&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a credit memo
&lt;/h3&gt;

&lt;p&gt;Credit memos are the AR counterpart to debit memos on the AP side. They follow a similar structure to invoices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/credit_memo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "credit_memo_date": "2025-07-15",
    "lines": [
      {
        "amount": 150,
        "product": "3560",
        "description": "Overpayment refund"
      }
    ],
    "entity": "54",
    "currency": "USD",
    "client": "24020"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Credit memos have their own void, reopen, and "mark as used" lifecycle, similar to invoices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bulk searching invoices by number
&lt;/h3&gt;

&lt;p&gt;When reconciling against an external system, you often need to look up invoices by their number rather than iterating through pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/invoice/bulk_search &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "invoice_numbers": ["INV-2025-001", "INV-2025-002", "INV-2025-003"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern exists for bills (&lt;code&gt;/ca/api/v1/bill/bulk_search&lt;/code&gt;), credit memos, and debit memos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Posting a journal entry
&lt;/h3&gt;

&lt;p&gt;Journal entries require balanced debit and credit transactions. Each transaction line must have either its debit amounts or credit amounts set, never both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/journal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "date": "2025-08-01",
    "type": "journal_entry",
    "transactions": [
      {
        "account": "180564",
        "debit_amount_native": 1000,
        "bank_description": "Monthly rent expense",
        "vendor": "34182",
        "department": "3613",
        "tags": [{"id": "7737"}]
      },
      {
        "account": "8856",
        "credit_amount_native": 1000,
        "bank_description": "Monthly rent expense",
        "department": "3436",
        "tags": [{"id": "8330"}]
      }
    ],
    "memo": "August 2025 rent",
    "entity": "54",
    "currency": "USD"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need to set &lt;code&gt;debit_amount_native&lt;/code&gt; or &lt;code&gt;credit_amount_native&lt;/code&gt;. The &lt;code&gt;_book&lt;/code&gt; and consolidation amounts are auto-computed from your exchange rates if omitted. Including a &lt;code&gt;reversal_date&lt;/code&gt; in the request body will automatically post a reversing journal on that date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Posting an intercompany journal entry
&lt;/h3&gt;

&lt;p&gt;Intercompany JEs have two key differences from standard journal entries: each transaction line specifies its own &lt;code&gt;entity&lt;/code&gt;, and the request includes a top-level &lt;code&gt;entities&lt;/code&gt; object with exchange rates for every entity involved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/intercompany_journal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "date": "2025-07-01",
    "type": "journal_entry",
    "transactions": [
      {
        "entity": 54,
        "account": "180564",
        "debit_amount_native": 1000,
        "debit_amount_book": 1000,
        "debit_amount": 1000,
        "bank_description": "Interco services - US entity"
      },
      {
        "entity": 5,
        "account": "7409",
        "credit_amount_native": 1000,
        "credit_amount_book": 728.33,
        "credit_amount": 1000,
        "bank_description": "Interco services - EU entity"
      },
      {
        "entity": 5,
        "account": "7405",
        "debit_amount_native": 1000,
        "debit_amount_book": 728.33,
        "debit_amount": 1000,
        "bank_description": "Interco elimination - EU"
      },
      {
        "entity": 54,
        "account": "8856",
        "credit_amount_native": 1000,
        "credit_amount_book": 1000,
        "credit_amount": 1000,
        "bank_description": "Interco elimination - US"
      }
    ],
    "memo": "Intercompany services Q3 2025",
    "currency": "USD",
    "entities": {
      "5": {
        "id": 5,
        "exchange_rate": 1.373,
        "exchange_rate_book": 0.728332
      },
      "54": {
        "id": 54,
        "exchange_rate": 1,
        "exchange_rate_book": 1
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;entities&lt;/code&gt; object is keyed by entity ID and contains the exchange rates for each entity. This is how Campfire handles multi-currency consolidation for groups with subsidiaries in different reporting currencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling financial statements
&lt;/h3&gt;

&lt;p&gt;The financial statements endpoints return structured report data without requiring any write operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Income statement&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/income_statement?start_date=2025-01-01&amp;amp;end_date=2025-03-31'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Balance sheet&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/balance_sheet?as_of_date=2025-03-31'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Trial balance&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/trial_balance?as_of_date=2025-03-31'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Cash flow statement&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/cash_flow?start_date=2025-01-01&amp;amp;end_date=2025-03-31'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Campfire also supports cash-basis variants of the income and operating statements for companies that need both accrual and cash-basis reporting, as well as comparative income statements and comparative balance sheets for period-over-period analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling the general ledger
&lt;/h3&gt;

&lt;p&gt;For detailed transaction-level reporting, the general ledger endpoint returns individual GL entries rather than summarized financial statements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://api.meetcampfire.com/ca/api/general_ledger?start_date=2025-01-01&amp;amp;end_date=2025-03-31'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for building custom reports or feeding data into a BI tool where you need line-level detail rather than aggregated statement data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Posting a bank feed
&lt;/h3&gt;

&lt;p&gt;For banking and fintech integrations, you can push transaction data directly into a bank account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/account/&lt;span class="o"&gt;{&lt;/span&gt;account_id&lt;span class="o"&gt;}&lt;/span&gt;/bank_feed &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "transactions": [
      {
        "date": "2025-07-15",
        "amount": -2500.00,
        "description": "Wire transfer to vendor",
        "reference": "WT-20250715-001"
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the endpoint you'd use if you're building a banking integration that feeds transactions into Campfire for reconciliation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up webhooks
&lt;/h3&gt;

&lt;p&gt;Rather than polling for changes, you can subscribe to events and receive push notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/integrations/api/v1/webhook &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://your-app.com/webhooks/campfire",
    "topics": [{"name": "invoice.created"}, {"name": "bill.updated"}],
    "active": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;GET /integrations/api/v1/webhook/events&lt;/code&gt; to list all available webhook topics. Each webhook includes a &lt;code&gt;token&lt;/code&gt; field you can use to verify incoming payloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voiding and reopening
&lt;/h3&gt;

&lt;p&gt;Both bills and invoices support a void/reopen lifecycle that preserves audit trails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Void a bill&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/bill/&lt;span class="o"&gt;{&lt;/span&gt;bill_id&lt;span class="o"&gt;}&lt;/span&gt;/void &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Reopen a voided bill&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/bill/&lt;span class="o"&gt;{&lt;/span&gt;bill_id&lt;span class="o"&gt;}&lt;/span&gt;/reopen_voided &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Void an invoice&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/invoice/&lt;span class="o"&gt;{&lt;/span&gt;invoice_id&lt;span class="o"&gt;}&lt;/span&gt;/void &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;

&lt;span class="c"&gt;# Void an invoice payment (without voiding the invoice itself)&lt;/span&gt;
curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.meetcampfire.com/ca/api/v1/invoice/&lt;span class="o"&gt;{&lt;/span&gt;invoice_id&lt;span class="o"&gt;}&lt;/span&gt;/void_payment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Token YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for integration builders because you need to handle these state transitions in your sync logic. A voided bill or invoice isn't deleted; it stays in the system with its status changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server for AI integrations
&lt;/h2&gt;

&lt;p&gt;Campfire also exposes an MCP (Model Context Protocol) server, which is worth knowing about if you're building AI-powered workflows on top of accounting data.&lt;/p&gt;

&lt;p&gt;The MCP server provides 12 tools covering chart of accounts, transactions, entities, vendors, departments, tags, income statements, balance sheets, cash flow, budgets, contracts, and aging reports. It authenticates with the same API token.&lt;/p&gt;

&lt;p&gt;Configuration for Claude Desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Campfire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://api.meetcampfire.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization:Token YOUR_API_TOKEN"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Cursor and VS Code, the server URL is &lt;code&gt;https://api.campfire.com/mcp&lt;/code&gt; (note: different domain from the Claude Desktop config, which uses &lt;code&gt;api.meetcampfire.com&lt;/code&gt;) with the token passed in headers. This enables natural language queries against your Campfire data, like pulling income statements or searching transactions without writing API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of integrating directly
&lt;/h2&gt;

&lt;p&gt;Campfire's API is well-structured and modern, but building a direct integration still involves the typical costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth management.&lt;/strong&gt; API keys need to be stored securely for each customer connection, and there's no OAuth flow, so you're managing static credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data model specifics.&lt;/strong&gt; Bills use &lt;code&gt;vendor&lt;/code&gt;, invoices use &lt;code&gt;client&lt;/code&gt;. Payment recording requires referencing journal transactions rather than just posting an amount. Exchange rates have a two-tier model. These are all reasonable design choices, but they mean your integration code is tightly coupled to Campfire's data model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance overhead.&lt;/strong&gt; The API is actively evolving. Endpoint paths don't follow a single convention (some use &lt;code&gt;/ca/api/&lt;/code&gt;, others &lt;code&gt;/ca/api/v1/&lt;/code&gt;, webhooks use &lt;code&gt;/integrations/api/v1/&lt;/code&gt;), so keeping up with changes requires ongoing attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-provider coverage.&lt;/strong&gt; If your customers also use QuickBooks, Xero, NetSuite, Sage, FreshBooks, or any of the dozens of other accounting platforms, you need to build and maintain each integration separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating with Campfire through Apideck
&lt;/h2&gt;

&lt;p&gt;Apideck provides a &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;unified Accounting API&lt;/a&gt; that normalizes data across 40+ accounting platforms, including Campfire. You write one integration and get coverage across all of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up for Apideck and enable the Campfire connector in your dashboard&lt;/li&gt;
&lt;li&gt;Use the pre-built &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;Vault&lt;/a&gt; component to let your users securely connect their Campfire account. Apideck handles credential storage.&lt;/li&gt;
&lt;li&gt;Make API calls using the unified Accounting API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example: listing invoices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoicesAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;campfire&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; invoices`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same code works for QuickBooks, Xero, NetSuite, FreshBooks, Exact, and every other connector in the Accounting category. Only the &lt;code&gt;serviceId&lt;/code&gt; changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Apideck normalizes
&lt;/h3&gt;

&lt;p&gt;Through the unified API, &lt;a href="https://www.apideck.com/integrations/campfire" rel="noopener noreferrer"&gt;Campfire&lt;/a&gt; data is mapped to 18+ standardized data models including invoices, bills, payments, customers, suppliers, ledger accounts, journal entries, tax rates, bank accounts, and balance sheet reports. You also get webhook support across all connected providers, official SDKs for Node.js, Python, TypeScript, PHP, and Go, and a pre-built auth component so you don't need to build credential management.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Go direct&lt;/strong&gt; if Campfire is the only accounting platform you need to support, you need access to Campfire-specific features like intercompany journal entries with multi-entity exchange rate handling, revenue recognition contracts, or bank reconciliation, or you're building an internal tool for a single Campfire instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Apideck&lt;/strong&gt; if your product needs to connect to multiple accounting platforms, you want to ship your Campfire integration in days instead of weeks, or you're a vertical SaaS, fintech, or embedded finance platform where multi-provider accounting connectivity is a product requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;For direct integration, start with the &lt;a href="https://docs.campfire.ai/quickstart" rel="noopener noreferrer"&gt;Campfire API quickstart&lt;/a&gt; and the four workflow guides covering &lt;a href="https://docs.campfire.ai/guides/accounts-payable" rel="noopener noreferrer"&gt;accounts payable&lt;/a&gt;, &lt;a href="https://docs.campfire.ai/guides/accounts-receivable" rel="noopener noreferrer"&gt;accounts receivable&lt;/a&gt;, &lt;a href="https://docs.campfire.ai/guides/journal-entries" rel="noopener noreferrer"&gt;journal entries&lt;/a&gt;, and &lt;a href="https://docs.campfire.ai/guides/ai-integration" rel="noopener noreferrer"&gt;MCP integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the unified API route, &lt;a href="https://app.apideck.com/signup" rel="noopener noreferrer"&gt;sign up for Apideck&lt;/a&gt;, enable the Campfire connector, and follow the &lt;a href="https://developers.apideck.com/apis/accounting/reference" rel="noopener noreferrer"&gt;Accounting API docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>campfireapi</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Accrual vs Cash-Based Accounting: Geographic Differences and What They Mean for Your API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Tue, 07 Jul 2026 16:21:43 +0000</pubDate>
      <link>https://dev.to/apideck/accrual-vs-cash-based-accounting-geographic-differences-and-what-they-mean-for-your-api-3aj4</link>
      <guid>https://dev.to/apideck/accrual-vs-cash-based-accounting-geographic-differences-and-what-they-mean-for-your-api-3aj4</guid>
      <description>&lt;p&gt;If you are building software that touches accounting data, you need to understand the difference between accrual and cash-based accounting. The accounting method your customer uses changes what the numbers in their ledger actually mean.&lt;/p&gt;

&lt;p&gt;And it gets more complex when you operate across borders. The rules are not the same in the US, the UK, and Europe. The thresholds differ. The defaults differ. What is mandatory for a company in Germany may be optional for the same size company in Texas.&lt;/p&gt;

&lt;p&gt;This post covers the fundamentals first, then the geographic nuances, then what all of this means when you are building accounting integrations through APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The Fundamentals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cash-based accounting
&lt;/h3&gt;

&lt;p&gt;Cash-based accounting records transactions when money moves. Revenue is recognized when payment hits your bank account. Expenses are recorded when the payment leaves. Simple. Predictable. Easy to reconcile against your bank statement.&lt;/p&gt;

&lt;p&gt;A freelancer sends an invoice in March. The client pays in May. Under cash-based accounting, that revenue shows up in May. There is no accounts receivable entry. There is no accrual adjustment at year end.&lt;/p&gt;

&lt;p&gt;This method works well for sole traders, small service businesses, and anyone who primarily deals in straightforward cash transactions. It gives you an accurate view of how much money is actually available right now.&lt;/p&gt;

&lt;p&gt;The downside is that it can distort the picture of how a business is performing. Revenue earned in one period may show up in another. A business can look profitable in one quarter simply because a batch of invoices happened to get paid, while the next quarter looks terrible because nothing came in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accrual-based accounting
&lt;/h3&gt;

&lt;p&gt;Accrual accounting records transactions when they are earned or incurred, regardless of when cash changes hands. Revenue is recognized when a sale is made. Expenses are recorded when the obligation is created. This is the matching principle in action: match revenue with the costs required to generate it.&lt;/p&gt;

&lt;p&gt;Same example. Freelancer sends an invoice in March. Under accrual accounting, the revenue is recorded in March, even if the payment does not arrive until May. There is an accounts receivable entry on the balance sheet until the cash comes in.&lt;/p&gt;

&lt;p&gt;This method gives a more accurate picture of a company's financial health over time. It is why both GAAP (Generally Accepted Accounting Principles, used in the US) and IFRS (International Financial Reporting Standards, used in most of the rest of the world) require accrual accounting for financial reporting.&lt;/p&gt;

&lt;p&gt;The trade-off is complexity. You need to track receivables, payables, accrued expenses, prepayments, and deferred revenue. Closing the books at period end takes more work. You need to estimate some amounts that are not yet final.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters for software
&lt;/h3&gt;

&lt;p&gt;If your application reads financial data from an accounting system, the accounting method determines what those numbers mean. A &lt;a href="https://apideck.com/blog/financial-statement-api" rel="noopener noreferrer"&gt;Profit &amp;amp; Loss report&lt;/a&gt; pulled from a cash-basis ledger will show different revenue figures than the same report pulled on an accrual basis, even for the same business in the same period.&lt;/p&gt;

&lt;p&gt;If you are building lending software, expense management, or financial analytics, you need to know which basis you are working with. Otherwise your risk models, forecasts, and dashboards are built on assumptions that may not hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Geographic Differences
&lt;/h2&gt;

&lt;p&gt;The rules around which businesses must use accrual accounting, which can choose, and which default to cash vary significantly across markets.&lt;/p&gt;

&lt;h3&gt;
  
  
  United States
&lt;/h3&gt;

&lt;p&gt;The US is relatively permissive. The IRS allows most small businesses to use the cash method as long as they meet the gross receipts test. Since the Tax Cuts and Jobs Act (TCJA), the threshold has been indexed for inflation. For tax years beginning in 2025, the threshold sits at $31 million in average annual gross receipts over the prior three-year period.&lt;/p&gt;

&lt;p&gt;Below that threshold, sole proprietors, S corporations, partnerships without C corporation partners, and qualified personal service corporations can all use cash-basis accounting. C corporations and partnerships with C corporation partners must use accrual if they exceed the threshold.&lt;/p&gt;

&lt;p&gt;There are additional nuances around inventory. Businesses that produce, purchase, or sell merchandise generally need to use accrual for inventory accounting. But the small business taxpayer exception lets qualifying businesses treat inventory as non-incidental supplies and stay on cash.&lt;/p&gt;

&lt;p&gt;The practical result: a large portion of US small and mid-market businesses use cash-basis accounting. QuickBooks holds roughly 80% market share among US small businesses by most estimates, and many of those customers run on a cash basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  United Kingdom
&lt;/h3&gt;

&lt;p&gt;The UK took a dramatic step in April 2024. HMRC made cash-basis accounting the default method for sole traders and partnerships. Previously, cash basis was opt-in with a turnover threshold of £150,000 to enter and £300,000 to stay. Those thresholds have been completely removed.&lt;/p&gt;

&lt;p&gt;Now, any sole trader or eligible partnership in the UK automatically uses cash-basis accounting unless they actively opt out and choose traditional (accrual) accounting on their Self Assessment tax return. This is a deliberate simplification move, aligned with HMRC's Making Tax Digital initiative.&lt;/p&gt;

&lt;p&gt;Limited companies and LLPs cannot use cash basis. They must use accrual accounting and comply with UK GAAP or IFRS. But for the vast majority of UK self-employed individuals, cash is the default.&lt;/p&gt;

&lt;p&gt;This means if you are building software for UK SMBs and sole traders, the most common scenario is cash-basis data flowing through the system. &lt;a href="https://apideck.com/blog/xero-integrations" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, FreeAgent, and other UK-popular platforms all support cash-basis reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Europe (EU and EEA)
&lt;/h3&gt;

&lt;p&gt;Europe is more fragmented. Each EU member state has its own rules, but the EU Accounting Directive provides a framework with simplified reporting regimes for SMEs and micro-businesses.&lt;/p&gt;

&lt;p&gt;Germany is strict on income tax accounting. The mandatory double-entry bookkeeping threshold under §141 AO applies at €800,000 in revenue or €80,000 in profit per year. These thresholds were raised from €600,000 and €60,000 respectively by Germany's Growth Opportunities Act (Wachstumschancengesetz), which came into force in March 2024. Sole proprietors and civil law companies (GbRs) below that threshold can use a simplified income-expenditure method (EÜR). Companies required to keep books, such as a GmbH, OHG, or UG, can apply for cash-based VAT treatment (Istversteuerung under §20 UStG) if their annual revenue is below €800,000. This threshold was also raised from €600,000 in January 2024. For income tax and statutory reporting purposes, most German businesses of any meaningful size use accrual accounting.&lt;/p&gt;

&lt;p&gt;The Netherlands allows businesses to opt for cash accounting for VAT purposes. The rules depend on entity type and turnover levels. But for corporate income tax and statutory reporting, accrual is standard.&lt;/p&gt;

&lt;p&gt;France similarly requires accrual accounting for most businesses that are under a real tax regime (régime réel). Simplified cash-like reporting is available for micro-enterprises below certain thresholds, but the default for any established business is accrual.&lt;/p&gt;

&lt;p&gt;The broader pattern in Europe: accrual is the default for any company required to file statutory accounts. IFRS applies to listed companies. National GAAP (which is generally aligned with or derived from the EU Accounting Directive) applies to most others. Cash accounting is an exception for the smallest businesses, not the norm.&lt;/p&gt;

&lt;p&gt;From a VAT perspective, the EU SME scheme caps the threshold at €85,000 for domestic VAT exemption and €100,000 for the cross-border scheme. These thresholds took effect from January 2025 under EU Council Directive 2020/285. They overlap in practice with the cash accounting question because the businesses small enough to qualify for VAT simplification are often the same ones that might use cash accounting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Australia and New Zealand
&lt;/h3&gt;

&lt;p&gt;Australia allows small businesses with an aggregated turnover under AUD 10 million to use cash accounting for GST purposes. Above that threshold, the non-cash (accruals) method is mandatory. For income tax, both cash and accrual methods are available to eligible businesses, though accrual remains the norm for companies required to prepare statutory financial statements under Australian Accounting Standards. New Zealand follows a similar pattern. Xero was built in New Zealand and its defaults and data models reflect this accrual-first environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means in practice
&lt;/h3&gt;

&lt;p&gt;If you are building a multi-market product that touches accounting data, you cannot assume a single accounting method across your customer base. US customers are more likely to be on cash basis. UK sole traders default to cash. European and ANZ customers are more likely to be on accrual. And even within a single market, the accounting method can vary by entity type and size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: What This Means for Your API Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem for developers
&lt;/h3&gt;

&lt;p&gt;When you connect to an accounting platform through its API, the data you get back is shaped by the accounting method the business uses. This creates several concrete challenges.&lt;/p&gt;

&lt;p&gt;Profit &amp;amp; Loss reports. QuickBooks Online lets you pull P&amp;amp;L reports on either a cash or accrual basis through its API, regardless of the company's default setting. Xero defaults to accrual and lets you switch in connected tools. Sage simply returns data in whatever method the business has configured. If your application does not account for this, you may be comparing cash-basis P&amp;amp;L data from one customer with accrual-basis data from another.&lt;/p&gt;

&lt;p&gt;Revenue recognition timing. On an accrual basis, revenue shows up when an invoice is created. On a cash basis, it shows up when the payment is received. If you are building a lending product that assesses revenue trends, mixing these two data sets without normalization will produce unreliable results.&lt;/p&gt;

&lt;p&gt;Accounts receivable and accounts payable. Under cash-basis accounting, AR and AP effectively do not exist in a meaningful sense. The ledger does not track outstanding invoices as balance sheet items. If your application expects to pull AR aging data to assess credit risk, that data may be empty or absent for a cash-basis customer.&lt;/p&gt;

&lt;p&gt;Balance sheet completeness. A cash-basis balance sheet is incomplete by definition. There are no accrued expenses, no deferred revenue, no prepayments. If your product needs a full picture of a company's financial position, you need accrual data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How accounting APIs handle this
&lt;/h3&gt;

&lt;p&gt;Each accounting platform handles the cash vs accrual distinction differently at the API level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks Online&lt;/a&gt; stores all transaction data and can generate reports on either basis. The QBO Reports API accepts an &lt;code&gt;accounting_method&lt;/code&gt; parameter that can be set to &lt;code&gt;Cash&lt;/code&gt; or &lt;code&gt;Accrual&lt;/code&gt;. This is powerful because it means you can always request accrual-basis reports even if the company's default is cash. The underlying journal entries contain enough data to reconstruct either view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v3/company/{companyId}/reports/ProfitAndLoss
  ?accounting_method=Accrual
  &amp;amp;start_date=2025-01-01
  &amp;amp;end_date=2025-03-31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt; defaults to accrual and stores transactions accordingly. When you pull reports through the Xero API, the default is accrual. Cash-basis reporting is handled by connected tools (like Fathom) that can reprocess the data. The API itself returns accrual-basis data. Invoice objects include status fields like &lt;code&gt;AUTHORISED&lt;/code&gt;, &lt;code&gt;PAID&lt;/code&gt;, and &lt;code&gt;VOIDED&lt;/code&gt; that let you reconstruct cash-basis timing if needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apideck.com/integrations/sage-business-cloud-accounting" rel="noopener noreferrer"&gt;Sage Business Cloud&lt;/a&gt; returns data based on the company's configuration. The API does not offer a toggle to switch between cash and accrual views. What you see is what the business has set up. If they run on cash, you get cash-basis data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; is accrual-first by design. As an enterprise ERP, it assumes accrual accounting. Revenue recognition schedules, multi-period allocations, and deferred revenue are all built into the data model. Cash-basis views are available through reporting configurations but the transaction data is fundamentally accrual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building for both: the unified API approach
&lt;/h3&gt;

&lt;p&gt;When you are integrating with multiple accounting platforms across multiple markets, the challenge compounds. You are dealing with different APIs, different data models, different authentication methods, and different assumptions about accounting basis.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;unified accounting API&lt;/a&gt; normalizes these differences. Instead of &lt;a href="https://apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;writing separate integration code&lt;/a&gt; for QuickBooks, Xero, Sage, and others, you connect once and get a standardized data model across all platforms.&lt;/p&gt;

&lt;p&gt;For the cash vs accrual problem specifically, a unified API can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expose the accounting method as a field on the company or organization object, so your application knows what basis the data represents&lt;/li&gt;
&lt;li&gt;Normalize invoice objects to include both the invoice date (accrual timing) and payment date (cash timing), giving you the data to reason about either method&lt;/li&gt;
&lt;li&gt;Standardize balance sheet accounts across platforms so your code does not need to handle the differences between how QBO, Xero, and Sage represent AR and AP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example using Apideck's &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;Accounting API&lt;/a&gt; to pull invoices with both timing signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Pull invoices with both accrual and cash timing data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoicesAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;updated_since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-01-01T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Each invoice includes:&lt;/span&gt;
&lt;span class="c1"&gt;// - invoice_date (when it was issued, accrual timing)&lt;/span&gt;
&lt;span class="c1"&gt;// - due_date (when payment is expected)&lt;/span&gt;
&lt;span class="c1"&gt;// - paid_date (when payment was received, cash timing)&lt;/span&gt;
&lt;span class="c1"&gt;// - status (DRAFT, AUTHORISED, PAID, VOIDED)&lt;/span&gt;
&lt;span class="c1"&gt;// - balance (outstanding amount)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invoice &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Issued: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice_date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// accrual&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Paid:   &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paid_date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// cash&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Balance: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the building blocks to handle both accounting methods in your application logic, regardless of which platform the customer uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling multi-market scenarios
&lt;/h3&gt;

&lt;p&gt;If your product serves customers in the US, UK, and Europe, here are the practical patterns to follow.&lt;/p&gt;

&lt;p&gt;Detect the accounting method. Always check the organization's accounting method configuration when a new customer connects. Do not assume. Store this alongside the connection metadata so your application logic can branch accordingly.&lt;/p&gt;

&lt;p&gt;Normalize revenue data. For analytics and reporting, decide whether your product works on a cash or accrual basis internally. Then normalize incoming data to match. If you need accrual-basis revenue but the customer runs on cash, you can often reconstruct accrual timing from invoice dates. The reverse (deriving cash from accrual) requires payment data, which most APIs expose.&lt;/p&gt;

&lt;p&gt;Handle missing data gracefully. Cash-basis customers may have sparse or empty AR/AP data. Your UI and logic need to handle this without breaking. Do not show an AR aging chart that is empty and confusing. Instead, adapt the experience based on the accounting method.&lt;/p&gt;

&lt;p&gt;Be explicit about basis in reports. If your product generates financial reports or exports data, always label the accounting method. A revenue chart that does not specify cash vs accrual is a chart that can be misread.&lt;/p&gt;

&lt;p&gt;Tax implications vary by region. If your product handles anything tax-related, be aware that the accounting method for tax purposes may differ from the method used for management reporting. A UK limited company must use accrual for statutory accounts but might use simplified reporting internally. A US business might use cash for tax but accrual for management purposes. The API data reflects the accounting system's configuration, not necessarily the tax reporting basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trial balance: where it all comes together
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;trial balance&lt;/a&gt; is the foundational report for any accounting integration. It is a list of all accounts with their debit and credit balances at a point in time. On an accrual basis, the trial balance includes accrued income, accrued expenses, prepayments, and deferred revenue. On a cash basis, many of these accounts either do not exist or carry zero balances.&lt;/p&gt;

&lt;p&gt;If your product relies on the trial balance for financial analysis, credit assessment, or automated bookkeeping, you need to understand which version you are looking at. A cash-basis trial balance can look very different from an accrual-basis trial balance for the same business in the same period.&lt;/p&gt;

&lt;p&gt;For platforms like QuickBooks that support both views, you can request the trial balance on either basis. For platforms that only return data in the configured method, you need to work with what you get or build conversion logic in your application layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Platform Support Reference
&lt;/h2&gt;

&lt;p&gt;The table below covers the full range of accounting systems and ERPs you are likely to encounter when building for US, UK, European, and ANZ markets. Cash basis column indicates whether the platform supports it at all; API basis toggle indicates whether you can request a specific basis via API parameter rather than relying on the company's configuration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Cash basis&lt;/th&gt;
&lt;th&gt;Accrual basis&lt;/th&gt;
&lt;th&gt;API basis toggle&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Primary markets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;QuickBooks Online&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (&lt;code&gt;accounting_method&lt;/code&gt; param)&lt;/td&gt;
&lt;td&gt;Varies (cash common for US SMBs)&lt;/td&gt;
&lt;td&gt;US, CA, UK, AU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuickBooks Desktop&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Set per company&lt;/td&gt;
&lt;td&gt;US, CA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Xero&lt;/td&gt;
&lt;td&gt;Reports only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;AU, NZ, UK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sage Business Cloud&lt;/td&gt;
&lt;td&gt;Config-dependent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;UK, IE, US&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sage Intacct&lt;/td&gt;
&lt;td&gt;Reports only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;US enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sage 50&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Set per company&lt;/td&gt;
&lt;td&gt;UK, US, CA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NetSuite&lt;/td&gt;
&lt;td&gt;Reports only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Via SuiteQL&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;Enterprise global&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MS Dynamics 365 BC&lt;/td&gt;
&lt;td&gt;Reports only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;Enterprise global&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAP Business One&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;Mid-market global&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreshBooks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Set per account&lt;/td&gt;
&lt;td&gt;US, CA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wave&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;US, CA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoho Books&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Report params only&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;Global SMB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreeAgent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Cash (sole traders)&lt;/td&gt;
&lt;td&gt;UK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact Online&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;NL, BE, EU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moneybird&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;NL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yuki&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;BE, NL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Odoo&lt;/td&gt;
&lt;td&gt;Via journal config&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accrual&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;QuickBooks Online is the only platform here that offers a genuine API-level basis toggle. You can request either cash or accrual regardless of how the company is configured. That makes it the most flexible platform for products that need to normalize data across a mixed customer base.&lt;/p&gt;

&lt;p&gt;Most other platforms return data in whatever basis the company has configured and offer no server-side switch. If you need cash-basis data from a Xero customer, you reconstruct it from payment dates on the invoice objects. If you need accrual from a Sage Business Cloud customer running on cash, that conversion happens in your code.&lt;/p&gt;

&lt;p&gt;Exact Online, Yuki, and SAP Business One are accrual-only. If you are building for Benelux or mid-market European buyers, assume accrual. There is no cash-basis configuration in these systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apideck.com/integrations/freeagent" rel="noopener noreferrer"&gt;FreeAgent&lt;/a&gt; is the UK outlier. It defaults to cash basis for sole traders, in line with HMRC's April 2024 change. If you are seeing sparse AR and AP data from UK FreeAgent connections, check whether the customer is a sole trader on cash before assuming a data issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deferred revenue and what it exposes about platform differences
&lt;/h3&gt;

&lt;p&gt;Deferred revenue is a good test case for how much any given platform understands about accrual accounting. It is a current liability: cash has been received, but the obligation to deliver the service or product has not yet been fulfilled. A SaaS company that collects $12,000 for an annual subscription on January 1 records $12,000 in deferred revenue on that date, then recognizes $1,000 per month as the subscription period passes. On a cash basis, none of that exists. The $12,000 shows up as revenue in January.&lt;/p&gt;

&lt;p&gt;The gap this creates for developers is concrete. If you pull P&amp;amp;L data from a SaaS company on cash-basis accounting, their January looks exceptional and the rest of the year looks flat. If you pull from the same company on accrual, you see $1,000 per month throughout the year. These are not just different accounting treatments of the same underlying reality. They tell completely different stories about the business.&lt;/p&gt;

&lt;p&gt;NetSuite has the most complete implementation of any platform in common use. Revenue recognition is a core module, not a bolted-on feature. You can pull recognition schedules directly from the API, query deferred revenue balances by contract or item, and get forecast data showing how deferred amounts will recognize over future periods. &lt;a href="https://apideck.com/integrations/sage-intacct" rel="noopener noreferrer"&gt;Sage Intacct&lt;/a&gt; also has dedicated revenue recognition, with contract-level schedules and multi-element arrangements exposed through the revenue management objects.&lt;/p&gt;

&lt;p&gt;QuickBooks Online supports deferred revenue in the sense that you can create a liability account and manually post journal entries against it. But there is no native recognition scheduling. You cannot query how much of a company's deferred revenue will recognize in Q3 through the QBO API. You get the account balance; the schedule lives in a spreadsheet.&lt;/p&gt;

&lt;p&gt;Xero has no native deferred revenue module. Teams handle it through manual journal entries or third-party tools. What you get from the Xero API is the balance in whatever liability account the accountant set up. There is no structured recognition data behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apideck.com/integrations/freshbooks" rel="noopener noreferrer"&gt;FreshBooks&lt;/a&gt; and &lt;a href="https://apideck.com/integrations/wave" rel="noopener noreferrer"&gt;Wave&lt;/a&gt; have no deferred revenue support. They are built for service businesses billing hourly or per project, where the concept rarely applies.&lt;/p&gt;

&lt;p&gt;Whether structured recognition schedules exist depends on the platform, not the business. NetSuite and Sage Intacct expose them via API. QBO, Xero, and most SMB platforms do not. If you need scheduled data, you either connect at the ERP layer or build your own estimation logic from invoice and contract data.&lt;/p&gt;

&lt;p&gt;A deferred revenue balance on the balance sheet is also not always reliable as a standalone metric. For QBO customers, it often reflects manually created journal entries rather than a systematically maintained schedule. The number may be accurate or it may be months stale. Before using it in credit assessment or financial modeling, treat it as a starting point.&lt;/p&gt;

&lt;p&gt;Zero deferred revenue in the ledger does not necessarily mean the business has none. For cash-basis customers, it simply does not appear. The information is absent by design, not because the business has no advance payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do about it
&lt;/h2&gt;

&lt;p&gt;If you are building a product that connects to &lt;a href="https://www.apideck.com/unified-apis/accounting" rel="noopener noreferrer"&gt;accounting platforms&lt;/a&gt;, treat the accounting method as a first-class concept in your data model. Detect it early. Handle both methods in your logic. Label your outputs clearly so nobody misreads a cash-basis revenue number as accrual.&lt;/p&gt;

&lt;p&gt;The businesses connecting to your product have no choice in the matter. Their accounting method is determined by their jurisdiction, their entity type, and their size. Your job is to meet them where they are and make the data useful regardless.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Integrate with the FreshBooks API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 06 Jul 2026 15:15:56 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-freshbooks-api-15l2</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-freshbooks-api-15l2</guid>
      <description>&lt;p&gt;FreshBooks is cloud-based accounting software popular with freelancers and small businesses. The FreshBooks API gives you programmatic access to invoices, clients, expenses, time tracking, and reports through a REST interface.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/integrations/freshbooks" rel="noopener noreferrer"&gt;Apideck FreshBooks connector&lt;/a&gt; handles authentication, pagination, and data normalization if you want to skip the manual work. It also lets you support 25+ accounting platforms with a single integration.&lt;/p&gt;

&lt;p&gt;This guide covers direct FreshBooks integration: authentication, the identity model, endpoints, pagination, webhooks, and the gotchas that trip people up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the FreshBooks API is
&lt;/h2&gt;

&lt;p&gt;The FreshBooks API is JSON-based REST. You can create invoices, manage clients, track expenses, log time, and pull reports.&lt;/p&gt;

&lt;p&gt;The API uses standard HTTP methods (GET, POST, PUT, DELETE) with JSON payloads, OAuth 2.0 for authentication, scope-based permissions, and webhooks for real-time notifications.&lt;/p&gt;

&lt;p&gt;Base URL: &lt;code&gt;https://api.freshbooks.com&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Integrate with FreshBooks
&lt;/h2&gt;

&lt;p&gt;SaaS companies integrate with FreshBooks to connect their platform to their customers' accounting workflows. Instead of asking users to export and import data manually, an integration syncs it automatically. For more on accounting integration patterns, see our guide on &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Say you have a project management tool that tracks billable hours. Without integration, users manually create invoices in FreshBooks based on their time entries. With integration, your tool generates draft invoices with the right client, line items, and rates.&lt;/p&gt;

&lt;p&gt;Other use cases: payment processors marking invoices as paid when payments clear, CRM systems syncing client data bidirectionally, e-commerce platforms creating invoices on orders, expense tools pushing approved expenses into FreshBooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;You need a FreshBooks account and a registered application before writing any code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Developer Account
&lt;/h3&gt;

&lt;p&gt;Sign up at &lt;a href="https://www.freshbooks.com" rel="noopener noreferrer"&gt;freshbooks.com&lt;/a&gt; if you don't have an account. Trial accounts work for development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register Your Application
&lt;/h3&gt;

&lt;p&gt;Go to the &lt;a href="https://my.freshbooks.com/#/developer" rel="noopener noreferrer"&gt;FreshBooks developer page&lt;/a&gt; and create a new application. You'll provide an application name (must be unique across all FreshBooks apps, shows on the authorization screen), a redirect URI (receives the authorization code after users grant access, must be HTTPS in production), and the scopes your app needs.&lt;/p&gt;

&lt;p&gt;FreshBooks generates a client ID and client secret after you save. Store the secret securely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;FreshBooks uses OAuth 2.0. The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User visits your authorization URL, gets redirected to FreshBooks&lt;/li&gt;
&lt;li&gt;User logs in and grants permission&lt;/li&gt;
&lt;li&gt;FreshBooks redirects back with an authorization code&lt;/li&gt;
&lt;li&gt;Your server exchanges the code for access and refresh tokens&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Authorization URL
&lt;/h3&gt;

&lt;p&gt;Redirect users here to start the OAuth flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://auth.freshbooks.com/oauth/authorize/
  ?response_type=code
  &amp;amp;client_id=YOUR_CLIENT_ID
  &amp;amp;redirect_uri=YOUR_REDIRECT_URI
  &amp;amp;scope=user:profile:read user:clients:read user:invoices:read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;scope&lt;/code&gt; parameter defines what permissions you're requesting. Request only what you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token Exchange
&lt;/h3&gt;

&lt;p&gt;After the user grants access, FreshBooks redirects to your redirect URI with a &lt;code&gt;code&lt;/code&gt; parameter. Exchange it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.freshbooks.com/auth/oauth/token'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "AUTHORIZATION_CODE",
    "redirect_uri": "YOUR_REDIRECT_URI"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;43200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user:profile:read user:clients:read user:invoices:read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234567890&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Lifetimes
&lt;/h3&gt;

&lt;p&gt;Access tokens expire after 12 hours. Refresh tokens never expire but are single-use. Every refresh gives you a new access token and a new refresh token. Store the new refresh token immediately because the old one is now invalid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.freshbooks.com/auth/oauth/token'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "grant_type": "refresh_token",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "refresh_token": "YOUR_REFRESH_TOKEN"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making Authenticated Requests
&lt;/h3&gt;

&lt;p&gt;Include the access token in the Authorization header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/auth/api/v1/users/me'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Identity Model
&lt;/h2&gt;

&lt;p&gt;This is where FreshBooks gets tricky. The API uses two different identifiers: &lt;code&gt;accountId&lt;/code&gt; and &lt;code&gt;businessId&lt;/code&gt;. You need to know when to use each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accounts vs Businesses
&lt;/h3&gt;

&lt;p&gt;FreshBooks evolved from an account-based architecture to a business-based one. The result is a split:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;accountId&lt;/code&gt; is for &lt;code&gt;/accounting&lt;/code&gt; endpoints (invoices, clients, expenses, payments). &lt;code&gt;businessId&lt;/code&gt; is for &lt;code&gt;/timetracking&lt;/code&gt; and &lt;code&gt;/projects&lt;/code&gt; endpoints.&lt;/p&gt;

&lt;p&gt;These are different values. You cannot interchange them. Using the wrong one causes silent failures or incorrect routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Your Identifiers
&lt;/h3&gt;

&lt;p&gt;Call the &lt;code&gt;/me&lt;/code&gt; endpoint to retrieve the user's identities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/auth/api/v1/users/me'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;business_memberships&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"business_memberships"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;168372&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;77128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zDmNq"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;businessId&lt;/code&gt; is &lt;code&gt;77128&lt;/code&gt; and &lt;code&gt;accountId&lt;/code&gt; is &lt;code&gt;zDmNq&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A single user can have multiple business memberships with different roles (owner, admin, manager, employee, contractor, client). Your integration needs to handle this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoint Patterns
&lt;/h3&gt;

&lt;p&gt;Accounting endpoints use &lt;code&gt;accountId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/accounting/account/{accountId}/invoices/invoices
/accounting/account/{accountId}/users/clients
/accounting/account/{accountId}/expenses/expenses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time tracking and project endpoints use &lt;code&gt;businessId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/timetracking/business/{businessId}/time_entries
/projects/business/{businessId}/projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Endpoints
&lt;/h2&gt;

&lt;p&gt;FreshBooks organizes its API around business entities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clients
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/users/clients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/users/clients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/users/clients/{clientId}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a single client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/users/clients/{clientId}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update a client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To delete a client, send a PUT request with &lt;code&gt;vis_state&lt;/code&gt; set to &lt;code&gt;1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/users/clients/12345'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "client": {
      "vis_state": 1
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/users/clients'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "client": {
      "fname": "Jane",
      "lname": "Doe",
      "email": "jane@example.com",
      "organization": "Doe Industries",
      "currency_code": "USD"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoices
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/invoices/invoices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/invoices/invoices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create an invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/invoices/invoices/{invoiceId}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get an invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/invoices/invoices/{invoiceId}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update an invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Invoice line items are not returned by default. You must explicitly request them with &lt;code&gt;include[]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?include[]=lines'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expenses
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/expenses/expenses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List expenses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/expenses/expenses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create an expense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/expenses/expenses/{expenseId}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get an expense&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Payments
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/payments/payments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List payments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/accounting/account/{accountId}/payments/payments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Record a payment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Time Entries
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/timetracking/business/{businessId}/time_entries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List time entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/timetracking/business/{businessId}/time_entries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a time entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Pagination
&lt;/h2&gt;

&lt;p&gt;FreshBooks uses page-based pagination with &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;per_page&lt;/code&gt; parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?page=1&amp;amp;per_page=100'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response includes pagination metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"invoices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"per_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1389&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch out: the API silently caps &lt;code&gt;per_page&lt;/code&gt; at 100. If you request &lt;code&gt;per_page=2000&lt;/code&gt;, you get 100 results with no warning. You need proper pagination to fetch complete datasets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAllInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://api.freshbooks.com/accounting/account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoices/invoices?page=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;per_page=100`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Searching and Filtering
&lt;/h2&gt;

&lt;p&gt;FreshBooks supports search parameters on list endpoints. The syntax is &lt;code&gt;search[field]=value&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find invoices for a specific client&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?search[customerid]=12345'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;

&lt;span class="c"&gt;# Find unpaid invoices&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?search[payment_status]=unpaid'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For "in" searches (matching multiple values):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find invoices with status 2 or 4&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?search[status][]=2&amp;amp;search[status][]=4'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available search parameters vary by endpoint. Check the API docs for each resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Includes
&lt;/h2&gt;

&lt;p&gt;Related data often requires extra API calls. FreshBooks provides an &lt;code&gt;include[]&lt;/code&gt; parameter to fetch related resources inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get invoices with line items&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?include[]=lines'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;

&lt;span class="c"&gt;# Get invoices with lines and allowed payment gateways&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'https://api.freshbooks.com/accounting/account/zDmNq/invoices/invoices?include[]=lines&amp;amp;include[]=allowed_gateways'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;include[]=lines&lt;/code&gt;, invoice responses omit line item details. This trips people up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate Limits
&lt;/h2&gt;

&lt;p&gt;FreshBooks does not publish specific numeric rate limits but will throttle requests if you make too many calls in a short period. The API returns HTTP 429 when rate limited.&lt;/p&gt;

&lt;p&gt;Implement exponential backoff on 429 responses, cache responses where appropriate, use includes to reduce call count, and batch operations when possible.&lt;/p&gt;

&lt;p&gt;FreshBooks reserves the right to disable apps that hit the API aggressively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks
&lt;/h2&gt;

&lt;p&gt;You can subscribe to webhooks for real-time notifications instead of polling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registering a Webhook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.freshbooks.com/events/account/zDmNq/events/callbacks'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "callback": {
      "event": "invoice.create",
      "uri": "https://your-server.com/webhooks/freshbooks"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;FreshBooks sends a verification code to your endpoint when you first register. You must verify ownership by sending the code back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s1"&gt;'https://api.freshbooks.com/events/account/zDmNq/events/callbacks/{callbackId}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_ACCESS_TOKEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "callback": {
      "verifier": "scADVVi5QuKuj5qTjVkbJNYQe7V7USpGd"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this verification code. You'll need it to verify incoming webhook signatures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook Payload
&lt;/h3&gt;

&lt;p&gt;When an event occurs, FreshBooks POSTs form-urlencoded data to your URI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;invoice.create&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1234567&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;zDmNq&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;business_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;77128&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;identity_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;712052&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="py"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;object_id&lt;/code&gt; is the ID of the affected resource. You'll need to call the API to get full details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signature Verification
&lt;/h3&gt;

&lt;p&gt;Each webhook includes an &lt;code&gt;X-FreshBooks-Hmac-SHA256&lt;/code&gt; header. Verify it using the verification code as the secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# FreshBooks sends form-urlencoded data, but calculates the signature
&lt;/span&gt;    &lt;span class="c1"&gt;# using a JSON representation. Convert all values to strings and serialize
&lt;/span&gt;    &lt;span class="c1"&gt;# to JSON. Note: Python's json.dumps adds spaces after : and , which matches
&lt;/span&gt;    &lt;span class="c1"&gt;# FreshBooks' format (e.g., {"key": "value", "key2": "value2"})
&lt;/span&gt;    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;calculated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;digestmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculated&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Supported Events
&lt;/h3&gt;

&lt;p&gt;Common webhook events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;client.create&lt;/td&gt;
&lt;td&gt;Client created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client.update&lt;/td&gt;
&lt;td&gt;Client updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client.delete&lt;/td&gt;
&lt;td&gt;Client deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;invoice.create&lt;/td&gt;
&lt;td&gt;Invoice created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;invoice.update&lt;/td&gt;
&lt;td&gt;Invoice updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;invoice.delete&lt;/td&gt;
&lt;td&gt;Invoice deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;invoice.sendByEmail&lt;/td&gt;
&lt;td&gt;Invoice emailed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;estimate.create&lt;/td&gt;
&lt;td&gt;Estimate created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;estimate.update&lt;/td&gt;
&lt;td&gt;Estimate updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;estimate.delete&lt;/td&gt;
&lt;td&gt;Estimate deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;payment.create&lt;/td&gt;
&lt;td&gt;Payment recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;payment.update&lt;/td&gt;
&lt;td&gt;Payment updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;payment.delete&lt;/td&gt;
&lt;td&gt;Payment deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expense.create&lt;/td&gt;
&lt;td&gt;Expense created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expense.update&lt;/td&gt;
&lt;td&gt;Expense updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expense.delete&lt;/td&gt;
&lt;td&gt;Expense deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;project.create&lt;/td&gt;
&lt;td&gt;Project created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;project.update&lt;/td&gt;
&lt;td&gt;Project updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;project.delete&lt;/td&gt;
&lt;td&gt;Project deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;time_entry.create&lt;/td&gt;
&lt;td&gt;Time entry created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;time_entry.update&lt;/td&gt;
&lt;td&gt;Time entry updated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;time_entry.delete&lt;/td&gt;
&lt;td&gt;Time entry deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FreshBooks supports many more events including &lt;code&gt;bill.*&lt;/code&gt;, &lt;code&gt;bill_vendor.*&lt;/code&gt;, &lt;code&gt;category.*&lt;/code&gt;, &lt;code&gt;credit_note.*&lt;/code&gt;, &lt;code&gt;item.*&lt;/code&gt;, &lt;code&gt;recurring.*&lt;/code&gt;, &lt;code&gt;service.*&lt;/code&gt;, and &lt;code&gt;tax.*&lt;/code&gt;. See the &lt;a href="https://www.freshbooks.com/api/webhooks" rel="noopener noreferrer"&gt;FreshBooks webhook documentation&lt;/a&gt; for the full list.&lt;/p&gt;

&lt;p&gt;You can subscribe to all events for a noun by using just the noun: &lt;code&gt;invoice&lt;/code&gt; subscribes to create, update, delete, and sendByEmail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;FreshBooks returns standard HTTP status codes with JSON error bodies: 200 for success, 400 for bad request (invalid parameters), 401 for unauthorized (invalid or expired token), 403 for forbidden (insufficient permissions), 404 for not found, 429 for rate limited, 500 for server error.&lt;/p&gt;

&lt;p&gt;Error responses include details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"errno"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1012&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"customerid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer not found."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"999999"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data Mapping Considerations
&lt;/h2&gt;

&lt;p&gt;When mapping your data structures to FreshBooks:&lt;/p&gt;

&lt;p&gt;Clients require either a first name/last name or an organization name. Email is optional but recommended. Clients are identified by their &lt;code&gt;userid&lt;/code&gt; field in responses.&lt;/p&gt;

&lt;p&gt;Invoices need line items with a &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;unit_cost&lt;/code&gt;. The &lt;code&gt;qty&lt;/code&gt; defaults to 1. Tax handling is complex and varies by region.&lt;/p&gt;

&lt;p&gt;Monetary values come back as nested objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234.56"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dates use ISO 8601 format (YYYY-MM-DD).&lt;/p&gt;

&lt;p&gt;FreshBooks uses &lt;code&gt;vis_state&lt;/code&gt; to track whether records are active (0), deleted (1), or archived (2). Records are soft-deleted by sending a PUT request with &lt;code&gt;vis_state: 1&lt;/code&gt;. You can restore deleted records by setting &lt;code&gt;vis_state&lt;/code&gt; back to &lt;code&gt;0&lt;/code&gt;. If you're updating a record, don't include &lt;code&gt;vis_state&lt;/code&gt; unless you intend to change it. FreshBooks processes &lt;code&gt;vis_state&lt;/code&gt; changes separately and will ignore other fields in the same request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to FreshBooks and 25+ Accounting APIs at Once
&lt;/h2&gt;

&lt;p&gt;Building a direct FreshBooks integration means handling OAuth token rotation, the dual identity model, silent pagination caps, and webhook signature verification. And that's one platform.&lt;/p&gt;

&lt;p&gt;If your product needs multiple accounting systems, each has its own quirks. &lt;a href="https://www.apideck.com/blog/how-to-integrate-your-app-with-quickbooks-online" rel="noopener noreferrer"&gt;QuickBooks uses a different OAuth flow&lt;/a&gt;. Xero has its own pagination. Sage requires managing multiple regional APIs. For more on these challenges, see our guide on &lt;a href="https://www.apideck.com/blog/erp-integration-for-fintech-and-saas-connecting-quickbooks-netsuite-and-sage" rel="noopener noreferrer"&gt;ERP integration for fintech and SaaS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com" rel="noopener noreferrer"&gt;Apideck&lt;/a&gt; provides a unified API that handles these differences. You write one integration and get FreshBooks, QuickBooks, Xero, NetSuite, and 25+ other accounting platforms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// List invoices from FreshBooks&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoicesAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;freshbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Same code works for QuickBooks, Xero, etc.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qbInvoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoicesAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apideck handles OAuth flows, token refresh, data normalization, and pagination for each connector. Your team builds features instead of maintaining integrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/signup" rel="noopener noreferrer"&gt;Get started with Apideck&lt;/a&gt; to connect FreshBooks and 25+ other platforms through a single Accounting API. For a comparison of FreshBooks against other options, check out our &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;top 15 accounting APIs to integrate with&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does FreshBooks provide a sandbox environment?
&lt;/h3&gt;

&lt;p&gt;No. Use a trial account or a separate development account for testing. Be careful not to send real invoices from test accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle multiple FreshBooks accounts for a single user?
&lt;/h3&gt;

&lt;p&gt;Users can belong to multiple businesses with different roles. Parse the &lt;code&gt;business_memberships&lt;/code&gt; array from the &lt;code&gt;/me&lt;/code&gt; endpoint and let users select which business to connect. Store both the &lt;code&gt;accountId&lt;/code&gt; and &lt;code&gt;businessId&lt;/code&gt; for each connected business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the API support bulk operations?
&lt;/h3&gt;

&lt;p&gt;No dedicated bulk endpoints. You'll need to make individual API calls. Implement rate limiting and error handling to avoid hitting limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle currency conversion?
&lt;/h3&gt;

&lt;p&gt;FreshBooks stores amounts in the currency specified for each client or invoice. Multi-currency transactions are supported, but currency conversion is not handled by the API. Your integration must handle conversion if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What scopes should I request?
&lt;/h3&gt;

&lt;p&gt;Request only the scopes your app needs. &lt;code&gt;user:profile:read&lt;/code&gt; is required to get identity information. For client management, add &lt;code&gt;user:clients:read&lt;/code&gt; and &lt;code&gt;user:clients:write&lt;/code&gt;. For invoices, add &lt;code&gt;user:invoices:read&lt;/code&gt; and &lt;code&gt;user:invoices:write&lt;/code&gt;. For expenses, add &lt;code&gt;user:expenses:read&lt;/code&gt;. For payments, add &lt;code&gt;user:payments:read&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Over-requesting scopes makes users less likely to authorize your app.&lt;/p&gt;

</description>
      <category>api</category>
      <category>freshbooksapi</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>How to Integrate with the MYOB API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:13:02 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-myob-api-3gf5</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-myob-api-3gf5</guid>
      <description>&lt;p&gt;MYOB dominates the accounting software market in Australia and New Zealand. If your SaaS product serves businesses in the ANZ region, your customers are probably asking for MYOB integration. This guide covers authentication (including the changes MYOB introduced in March 2025), the ongoing deprecation of AccountRight Classic, and the practical details of working with the API.&lt;/p&gt;

&lt;p&gt;If you'd rather skip the complexity of building direct integrations, Apideck offers a &lt;a href="https://www.apideck.com/integrations/myob" rel="noopener noreferrer"&gt;MYOB connector&lt;/a&gt; through our Unified Accounting API that handles OAuth, token management, and data normalization across MYOB and 30+ other accounting platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the MYOB Business API?
&lt;/h2&gt;

&lt;p&gt;The MYOB Business API is a REST-based JSON API for reading and writing data from MYOB AccountRight and MYOB Business company files. It works with both local desktop installations and cloud-hosted files, though the authentication flow differs between the two.&lt;/p&gt;

&lt;p&gt;The API covers core accounting functions: contacts (customers, suppliers, employees), invoices, bills, payments, inventory, general ledger accounts, journal entries, tax codes, and payroll. It uses OAuth 2.0 for cloud file authentication and supports OData query parameters for filtering and sorting results.&lt;/p&gt;

&lt;p&gt;MYOB has consolidated its product lines under the "MYOB Business" branding. The AccountRight and Essentials product lines are being phased out for new customers, but the API remains consistent across all three. If you see references to "AccountRight API" in older documentation, you're looking at the same API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why integrate with MYOB?
&lt;/h2&gt;

&lt;p&gt;MYOB holds significant market share among Australian and New Zealand SMBs. For vertical SaaS products, fintech platforms, and B2B tools targeting the ANZ market, MYOB integration is often a requirement.&lt;/p&gt;

&lt;p&gt;Consider an expense management SaaS. Without integration, users export transactions, manually create bills in MYOB, and reconcile by hand. With a MYOB integration, the app can push approved expenses directly to the ledger, create supplier records, and apply the correct tax codes automatically. Users save hours of double entry and reduce errors.&lt;/p&gt;

&lt;p&gt;The same logic applies to invoicing platforms, payment reconciliation tools, inventory management systems, and payroll applications. Integration eliminates manual data transfer and makes your product fit into existing accounting workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registration and API keys
&lt;/h2&gt;

&lt;p&gt;Before writing any code, register for the MYOB Developer Program at &lt;a href="https://developer.myob.com" rel="noopener noreferrer"&gt;developer.myob.com&lt;/a&gt;. Registration is free and gives you access to the MY.MYOB portal where you create API keys.&lt;/p&gt;

&lt;p&gt;Once your developer account is approved, log into MY.MYOB and navigate to the Developer tab. Register your application by providing a name and redirect URI. MYOB generates a client ID (API Key) and client secret that you use for OAuth authentication.&lt;/p&gt;

&lt;p&gt;The redirect URI must match exactly what you register. MYOB is strict about this—any mismatch causes authentication failures.&lt;/p&gt;

&lt;p&gt;MYOB offers different developer program tiers with varying API call limits. The default rate limit is 8 requests per second and 1,000,000 requests per day per API key. You can only have 2 active API keys at once per developer account. Using a third key automatically deactivates one of the others, which causes confusing rate limit errors if you're unaware of this restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: the March 2025 changes
&lt;/h2&gt;

&lt;p&gt;MYOB overhauled its OAuth flow in March 2025. If you're building a new integration, you must use the new scopes and authentication requirements. Keys created before March 2025 still use the old scopes, but any new integration needs to follow the updated process.&lt;/p&gt;

&lt;h3&gt;
  
  
  New granular scopes
&lt;/h3&gt;

&lt;p&gt;The old &lt;code&gt;CompanyFile&lt;/code&gt; scope is deprecated for new API keys. MYOB now requires granular scopes that specify which data categories your app needs access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sme-company-file&lt;/code&gt; — required for all access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-general-ledger&lt;/code&gt; — accounts, journals, tax codes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-sale&lt;/code&gt; — invoices, customer payments, quotes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-purchase&lt;/code&gt; — bills, supplier payments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-contact&lt;/code&gt; — customers, suppliers, employees&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-inventory&lt;/code&gt; — items, locations, adjustments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sme-payroll&lt;/code&gt; — employee payroll details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request only the scopes your application actually needs. The authorization screen shows users exactly what data you're requesting access to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Admin-only authorization
&lt;/h3&gt;

&lt;p&gt;As of March 2025, only administrator users of a MYOB company file can approve OAuth requests. This is a security enhancement. If your users attempt to authorize with a non-admin account, the flow fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Company file GUID retrieval
&lt;/h3&gt;

&lt;p&gt;The old endpoint &lt;code&gt;GET https://api.myob.com/accountright/&lt;/code&gt; no longer returns company files for new API keys. Instead, MYOB returns the company file GUID (businessId) in the redirect URI after successful authorization.&lt;/p&gt;

&lt;p&gt;You must include &lt;code&gt;prompt=consent&lt;/code&gt; in your authorization URL to ensure the businessId is returned. The authorization URL looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://secure.myob.com/oauth2/account/authorize
  ?client_id=YOUR_CLIENT_ID
  &amp;amp;redirect_uri=https://yourapp.com/redirect
  &amp;amp;response_type=code
  &amp;amp;scope=sme-company-file sme-contact sme-sale
  &amp;amp;prompt=consent
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the user authorizes, MYOB redirects to your URI with both a code and businessId parameter. Use the code to exchange for access and refresh tokens. Store the businessId as the company file identifier for all subsequent API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token lifecycle
&lt;/h3&gt;

&lt;p&gt;Access tokens expire in approximately 20 minutes. When you receive tokens, MYOB includes an expiry timestamp. Implement token refresh before expiration to maintain uninterrupted access. The refresh token request uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://secure.myob.com/oauth2/v1/authorize
Content-Type: application/x-www-form-urlencoded

client_id=YOUR_CLIENT_ID
&amp;amp;client_secret=YOUR_SECRET
&amp;amp;refresh_token=REFRESH_TOKEN
&amp;amp;grant_type=refresh_token
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store tokens securely and never log them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key API endpoints
&lt;/h2&gt;

&lt;p&gt;The MYOB Business API organizes endpoints by business function. The base URL for cloud files is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://arl2.api.myob.com/accountright/{company_file_id}/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local desktop files, the API runs on &lt;code&gt;localhost:8080/accountright/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contacts
&lt;/h3&gt;

&lt;p&gt;Contacts cover customers, suppliers, and employees.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Customer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer records for invoicing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Supplier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Supplier records for bills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Employee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee records for payroll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Personal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Other contacts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Contacts require a &lt;code&gt;Name&lt;/code&gt; field at minimum. Each contact gets a unique UID that you use when creating invoices or bills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales
&lt;/h3&gt;

&lt;p&gt;Sales endpoints handle customer-facing transactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Invoice/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Product-based invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Invoice/Service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service-based invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/CustomerPayment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payments received from customers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Quote/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quotes for products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/CreditSettlement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credit note applications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When creating invoices, you POST to the specific invoice type endpoint. The generic &lt;code&gt;/Sale/Invoice&lt;/code&gt; endpoint only supports GET requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Purchases
&lt;/h3&gt;

&lt;p&gt;Purchase endpoints mirror the sales structure for supplier transactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Bill/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bills for purchased items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Bill/Service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service bills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/SupplierPayment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payments to suppliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Order/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Purchase orders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  General Ledger
&lt;/h3&gt;

&lt;p&gt;Ledger endpoints provide access to the chart of accounts and financial data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/Account&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chart of accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/TaxCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tax rate definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/JournalTransaction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Posted journal entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/GeneralJournal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual journal entry creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/Category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tracking categories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Inventory
&lt;/h3&gt;

&lt;p&gt;Inventory endpoints manage products and stock.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products and services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Adjustment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stock adjustments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Warehouse locations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Request headers and versioning
&lt;/h2&gt;

&lt;p&gt;The API requires specific headers for all requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer ACCESS_TOKEN
x-myobapi-key: YOUR_CLIENT_ID
x-myobapi-version: v2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always specify &lt;code&gt;x-myobapi-version: v2&lt;/code&gt;. The default is v1, which lacks GUID support and causes problems. This is a common source of errors for new integrations.&lt;/p&gt;

&lt;p&gt;For cloud files, include the company file token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;x-myobapi-cftoken: BASE64_ENCODED(username:password)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cftoken contains the company file username and password, base64 encoded with a colon separator. For files authenticated via OAuth 2.0 with admin access, this may not be required, but older implementations and desktop files need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data mapping considerations
&lt;/h2&gt;

&lt;p&gt;When syncing data between your application and MYOB, keep these mapping issues in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoice structure
&lt;/h3&gt;

&lt;p&gt;MYOB supports five invoice types: Item, Service, Professional, TimeBilling, and Miscellaneous. Each has a different line item structure. Most integrations use Item or Service invoices.&lt;/p&gt;

&lt;p&gt;Line items require references to existing inventory items or tax codes. You cannot create an invoice with inline item definitions. Fetch or create the required items first, then reference them by UID in the invoice payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tax codes
&lt;/h3&gt;

&lt;p&gt;MYOB stores tax rates as TaxCode objects with UIDs. Common Australian tax codes include GST (10%), GST Free, and Input Taxed. Your application needs to map its tax logic to the appropriate MYOB tax codes, which you can retrieve from &lt;code&gt;/GeneralLedger/TaxCode&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account mapping
&lt;/h3&gt;

&lt;p&gt;Line items on invoices and bills link to ledger accounts. MYOB uses account numbers and UIDs. Retrieve the chart of accounts via &lt;code&gt;/GeneralLedger/Account&lt;/code&gt; to map your categories to the correct accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Currency
&lt;/h3&gt;

&lt;p&gt;MYOB supports multi-currency transactions. Currency codes follow ISO 4217. If your customers use foreign currencies, check whether their MYOB file has multi-currency enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling and rate limits
&lt;/h2&gt;

&lt;p&gt;The API returns standard HTTP status codes: 200 for success, 201 for created, 400 for bad request, 401 for authentication errors, and 404 for not found.&lt;/p&gt;

&lt;p&gt;Error responses include JSON with error codes and messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ValidationError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Customer UID is required."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate limiting
&lt;/h3&gt;

&lt;p&gt;The default limits are 8 requests per second and 1,000,000 requests per day per API key. When you hit a rate limit, the API returns a 403 status (not 429, which can be confusing). Implement exponential backoff and retry logic.&lt;/p&gt;

&lt;p&gt;The 29-second timeout limit means long-running queries may fail. If you're fetching large datasets, use pagination and OData filters to reduce response times.&lt;/p&gt;

&lt;h3&gt;
  
  
  OData filtering
&lt;/h3&gt;

&lt;p&gt;The API supports OData query parameters for filtering results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;orderby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Number&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;GeneralLedger&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s1"&gt;'Asset'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field names are case-sensitive. A common mistake is using &lt;code&gt;number&lt;/code&gt; instead of &lt;code&gt;Number&lt;/code&gt; or &lt;code&gt;isActive&lt;/code&gt; instead of &lt;code&gt;IsActive&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deprecation: AccountRight Classic
&lt;/h2&gt;

&lt;p&gt;MYOB is retiring AccountRight Classic (v19 and earlier) on February 28, 2026. After this date, Classic company files become read-only. Users won't be able to create transactions, only view historical data.&lt;/p&gt;

&lt;p&gt;This matters for your integration if you support customers running older AccountRight versions. After February 2026, those files will not accept new invoices or payments via the API. Affected customers need to migrate to the current AccountRight or MYOB Business product.&lt;/p&gt;

&lt;p&gt;The API for modern AccountRight and MYOB Business is unaffected. This deprecation only impacts the legacy Classic versions that predated the current API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your integration
&lt;/h2&gt;

&lt;p&gt;MYOB does not provide sandbox environments with demo data. You need to set up test data yourself, which requires understanding both the API and MYOB's accounting workflows.&lt;/p&gt;

&lt;p&gt;Your options: create a trial MYOB Business account and populate it with test data manually, use a dedicated company file in a customer's MYOB system (with their permission), or set up a local AccountRight installation for development.&lt;/p&gt;

&lt;p&gt;Start with read operations to verify your authentication works. Fetch company info, then retrieve customers and accounts. Once reads work, test creating a simple customer contact before moving to invoices.&lt;/p&gt;

&lt;p&gt;Use MYOB's Postman collection to experiment with API calls before writing code. The collection is available from the developer portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unified API approach
&lt;/h2&gt;

&lt;p&gt;Building direct MYOB integrations requires handling OAuth flows, token management, data normalization, and ongoing API maintenance. If MYOB updates their authentication requirements (as they did in March 2025), you need to update your integration.&lt;/p&gt;

&lt;p&gt;A unified API provider like Apideck handles this complexity. Instead of building separate integrations for each accounting platform your customers use, you build one integration against a normalized API. The provider manages authentication, handles API updates, and normalizes data structures across platforms. For a detailed comparison of approaches, see our guide on &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integrations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach works well when you need to support multiple accounting platforms or when you want to reduce ongoing maintenance burden. For teams focused on building core product features rather than becoming accounting API experts, unified APIs can significantly reduce integration time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I access multiple company files with one integration?
&lt;/h3&gt;

&lt;p&gt;Yes. Each company file requires separate OAuth authorization, but your integration can store multiple sets of tokens and company file GUIDs. When making API calls, use the appropriate token and businessId for the target company file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does MYOB support webhooks?
&lt;/h3&gt;

&lt;p&gt;No. The API does not provide webhooks for real-time notifications. You need to poll endpoints to detect changes. Use the &lt;code&gt;$filter&lt;/code&gt; parameter with timestamps to fetch only modified records since your last sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when AccountRight releases a new version?
&lt;/h3&gt;

&lt;p&gt;MYOB releases AccountRight updates regularly, typically monthly. Each release includes a decommission date for older versions, usually 2-3 months after the new release. Plan to update your integration testing when new versions ship, as some API behaviors may change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I email invoices through the API?
&lt;/h3&gt;

&lt;p&gt;Yes. Endpoints like &lt;code&gt;/Sale/Invoice/Item/{invoice_uid}/email&lt;/code&gt; let you trigger invoice emails directly from the API. The company file must be configured to send emails via AccountRight, and this only works for online files.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between AccountRight and MYOB Business?
&lt;/h3&gt;

&lt;p&gt;MYOB Business is the current product branding that replaces AccountRight and Essentials for new customers. The API is the same. AccountRight files show &lt;code&gt;UIAccessFlags: 3&lt;/code&gt; while Essentials files show &lt;code&gt;UIAccessFlags: 2&lt;/code&gt;. Your integration should work with both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Register for a &lt;a href="https://developer.myob.com" rel="noopener noreferrer"&gt;MYOB Developer account&lt;/a&gt;, create an API key, and start with the authentication flow. Read the endpoint documentation for contacts and invoices, since those are the most common integration points.&lt;/p&gt;

&lt;p&gt;If you want to accelerate your timeline, &lt;a href="https://www.apideck.com/signup" rel="noopener noreferrer"&gt;start with Apideck&lt;/a&gt; to connect MYOB and other accounting platforms through a single unified API.&lt;/p&gt;

&lt;p&gt;For more on building accounting integrations, see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate with in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/the-complete-guide-to-accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;The Complete Guide to Accounting API Integrations for Fintech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/how-to-integrate-sage-business-cloud-accounting-api" rel="noopener noreferrer"&gt;How to Integrate Sage Business Cloud Accounting API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>fintech</category>
      <category>saas</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Integrate with the Odoo API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:08:39 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-odoo-api-jp1</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-odoo-api-jp1</guid>
      <description>&lt;h2&gt;
  
  
  How Odoo's API Works
&lt;/h2&gt;

&lt;p&gt;Odoo exposes three API protocols: XML-RPC, JSON-RPC, and a REST API (added in Odoo 17). All three access the same underlying ORM layer — the same models, fields, and methods you'd use in a Python module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deprecation warning:&lt;/strong&gt; Odoo has announced that both XML-RPC (&lt;code&gt;/xmlrpc&lt;/code&gt;, &lt;code&gt;/xmlrpc/2&lt;/code&gt;) and JSON-RPC (&lt;code&gt;/jsonrpc&lt;/code&gt;) are scheduled for removal in Odoo 20 (targeted for fall 2026). Odoo 19 introduced a replacement called the JSON-2 API, which uses bearer token authentication and standard HTTP conventions. If you're starting a new integration today targeting Odoo 17+, it's worth checking the JSON-2 API docs before committing to XML-RPC. For integrations that need to support older versions (14–18), XML-RPC remains the most compatible choice.&lt;/p&gt;

&lt;p&gt;The key thing to understand regardless of protocol: Odoo's API is model-driven. Almost everything is a CRUD operation against a named model — &lt;code&gt;account.move&lt;/code&gt; for invoices, &lt;code&gt;res.partner&lt;/code&gt; for contacts, &lt;code&gt;account.account&lt;/code&gt; for the chart of accounts. Once you understand this pattern, working with any Odoo module follows the same playbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Odoo uses a two-step authentication process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call the &lt;code&gt;authenticate&lt;/code&gt; method to get a user ID (uid)&lt;/li&gt;
&lt;li&gt;Use that uid plus your API key or password on all subsequent calls
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xmlrpc.client&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourodoo.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key_or_password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Use API keys in production
&lt;/span&gt;
&lt;span class="c1"&gt;# Step 1: Get the uid
&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/common&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Create the models proxy
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API keys vs passwords.&lt;/strong&gt; Odoo 14+ supports dedicated API keys (Settings &amp;gt; Technical &amp;gt; API Keys). Always use an API key in production rather than a user password. API keys can be scoped and revoked independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The uid is per-session.&lt;/strong&gt; It's a stable identifier for the user account, not a session token that expires. You can store it and reuse it, but re-authenticate if you switch credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access rights matter.&lt;/strong&gt; The uid you authenticate with determines what models and records you can read or write. If you're hitting access errors, check the user's security groups in Odoo — not just the API code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core API Methods
&lt;/h2&gt;

&lt;p&gt;Every Odoo model supports the same set of methods. These five cover the vast majority of integration use cases:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;search_read&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The workhorse method. Searches records matching a domain filter and returns field values in a single call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get all posted (validated) customer invoices from the last 30 days
&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-02-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always specify &lt;code&gt;fields&lt;/code&gt; explicitly. Omitting it returns every field on the model, which is slow and produces a lot of noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;create&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Creates one record and returns its ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-03-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Consulting services - March 2025&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[0, 0, {...}]&lt;/code&gt; syntax is Odoo's "One2many command" for creating nested records inline. It's unusual but consistent — you'll see it everywhere relational fields appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;write&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Updates one or more records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PO-2025-001&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;unlink&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Deletes records. Use with care — Odoo often prevents deletion of posted records to preserve audit trails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unlink&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;execute_kw&lt;/code&gt; with custom methods
&lt;/h3&gt;

&lt;p&gt;Some actions (like posting an invoice) aren't CRUD — they're state transitions. You call these as named methods on the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Post (validate) a draft invoice
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accounting Module: Key Models
&lt;/h2&gt;

&lt;p&gt;Here's a map of the models you'll use most when integrating with Odoo Accounting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoices and Bills — &lt;code&gt;account.move&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This single model handles customer invoices, vendor bills, credit notes, and journal entries. The &lt;code&gt;move_type&lt;/code&gt; field distinguishes them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;move_type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_invoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_invoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor bill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_refund&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer credit note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_refund&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor credit note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_receipt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sales receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_receipt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Purchase receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;entry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Journal entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key fields to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# Invoice number (e.g. INV/2025/0042)
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# See table above
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# draft, posted, cancel
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# not_paid, in_payment, paid, partial, reversed
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# Customer or vendor
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date_due&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_untaxed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_tax&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_residual&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Outstanding balance
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# External reference / PO number
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoice Lines — &lt;code&gt;account.move.line&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Individual line items on an invoice. You can query these directly for detailed reporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move.line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_subtotal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tax_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Payments — &lt;code&gt;account.payment&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Records of money in/out. When a payment is registered against an invoice, Odoo creates a reconciliation between the payment and the invoice line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get all customer payments
&lt;/span&gt;&lt;span class="n"&gt;payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inbound&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To register a payment against a specific invoice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create payment
&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inbound&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-03-10&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;INV/2025/0042&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Validate the payment
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chart of Accounts — &lt;code&gt;account.account&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deprecated&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reconcile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;account_type&lt;/code&gt; field (Odoo 16+) replaced the old &lt;code&gt;user_type_id&lt;/code&gt; relation. The full list of values is: &lt;code&gt;asset_receivable&lt;/code&gt;, &lt;code&gt;asset_cash&lt;/code&gt;, &lt;code&gt;asset_current&lt;/code&gt;, &lt;code&gt;asset_non_current&lt;/code&gt;, &lt;code&gt;asset_prepayments&lt;/code&gt;, &lt;code&gt;asset_fixed&lt;/code&gt;, &lt;code&gt;liability_payable&lt;/code&gt;, &lt;code&gt;liability_credit_card&lt;/code&gt;, &lt;code&gt;liability_current&lt;/code&gt;, &lt;code&gt;liability_non_current&lt;/code&gt;, &lt;code&gt;equity&lt;/code&gt;, &lt;code&gt;equity_unaffected&lt;/code&gt;, &lt;code&gt;income&lt;/code&gt;, &lt;code&gt;income_other&lt;/code&gt;, &lt;code&gt;expense&lt;/code&gt;, &lt;code&gt;expense_depreciation&lt;/code&gt;, &lt;code&gt;expense_direct_cost&lt;/code&gt;, &lt;code&gt;off_balance&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contacts — &lt;code&gt;res.partner&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Both customers and vendors live here. The &lt;code&gt;customer_rank&lt;/code&gt; and &lt;code&gt;supplier_rank&lt;/code&gt; fields indicate their commercial relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;res.partner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer_rank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;street&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;country_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;property_payment_term_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with the JSON-RPC Interface
&lt;/h2&gt;

&lt;p&gt;If you prefer JSON (or you're working in a language without a mature XML-RPC client), the JSON-RPC endpoint works just as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/web/dataset/call_kw`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Authenticate&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;execute_kw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account.move&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;move_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out_invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]]],&lt;/span&gt;
    &lt;span class="na"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partner_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount_total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with the Native REST API (Odoo 17+)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This section covers the built-in REST API shipped with Odoo 17 and 18 — not any of the third-party REST modules available on the Odoo App Store. The native REST API is experimental as of Odoo 18 and has limited official documentation. For production integrations targeting Odoo 16 or earlier, use XML-RPC or JSON-RPC instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;The REST API authenticates using your API key as a bearer token. Generate one in Odoo under Preferences &amp;gt; Account Security &amp;gt; New API Key, then pass it in every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;your_api_key&amp;gt;
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Odoo instance hosts multiple databases, add the database name as a header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Odoo-Database: your_database_name
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reading Records (GET)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourodoo.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Get all posted customer invoices
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;],[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating Records (POST)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-03-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Consulting services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;new_invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;invoice_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling Methods
&lt;/h3&gt;

&lt;p&gt;State transitions like posting an invoice require calling a named method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/action_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The JSON-2 API (Odoo 19+)
&lt;/h3&gt;

&lt;p&gt;Odoo 19 introduced a cleaner replacement for XML-RPC and JSON-RPC called the JSON-2 API, located at &lt;code&gt;/json/2/{model}/{method}&lt;/code&gt;. It uses the same bearer token auth but with named arguments in the request body instead of positional ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/json/2/account.move/search_read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Odoo-Database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_database_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON-2 API is the long-term direction for Odoo's external API. If you're on Odoo 19 or planning to migrate, it's worth building against this rather than XML-RPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination and Performance
&lt;/h2&gt;

&lt;p&gt;Odoo doesn't paginate automatically. You control it with &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;all_invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id asc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;all_invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_invoices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few performance tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use &lt;code&gt;order: 'id asc'&lt;/code&gt; when paginating to ensure stable ordering&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;limit&lt;/code&gt; under 500 — larger batches increase memory pressure on the server&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;search&lt;/code&gt; (returns IDs only) then &lt;code&gt;read&lt;/code&gt; (fetches specific fields by IDs) instead of &lt;code&gt;search_read&lt;/code&gt; when you need to pre-filter a large set before loading fields&lt;/li&gt;
&lt;li&gt;Avoid fetching binary fields (like &lt;code&gt;image_1920&lt;/code&gt;) unless you actually need them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling Odoo Versions
&lt;/h2&gt;

&lt;p&gt;Odoo's API surface changes between versions. The main gotchas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 14 to 16:&lt;/strong&gt; The account type system changed from a relational &lt;code&gt;user_type_id&lt;/code&gt; field to a direct &lt;code&gt;account_type&lt;/code&gt; string field. If you support multiple Odoo versions, you'll need to branch on this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 17:&lt;/strong&gt; A REST API was added alongside the existing XML-RPC and JSON-RPC protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 19+:&lt;/strong&gt; Odoo announced the deprecation of both the XML-RPC and JSON-RPC endpoints, with removal planned for Odoo 20 (fall 2026). Odoo 19 introduced a JSON-2 API as the replacement.&lt;/p&gt;

&lt;p&gt;You can check the server version via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;version_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Returns e.g. {'server_version': '16.0', 'server_version_info': [16, 0, 0, 'final', 0, '']}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;Odoo returns errors inside the XML-RPC fault mechanism or the JSON-RPC error field. In Python, XML-RPC errors surface as &lt;code&gt;xmlrpc.client.Fault&lt;/code&gt; exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fault&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Odoo error &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;faultCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;faultString&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common error patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AccessError&lt;/code&gt; — the user doesn't have permission on that model or record&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValidationError&lt;/code&gt; — a required field is missing or a constraint failed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UserError&lt;/code&gt; — a business rule blocked the operation (e.g. trying to delete a posted invoice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always log the full &lt;code&gt;faultString&lt;/code&gt; — Odoo includes a Python traceback in it, which makes debugging much faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks and Real-time Sync
&lt;/h2&gt;

&lt;p&gt;Odoo doesn't have native webhooks in its community edition. For real-time sync, your options are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Polling&lt;/strong&gt; — query for records updated since a timestamp using &lt;code&gt;write_date&lt;/code&gt; or &lt;code&gt;create_date&lt;/code&gt; fields&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Odoo Automation&lt;/strong&gt; — create a scheduled action or server action that calls an external URL when a record changes (Enterprise or custom module)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database-level triggers&lt;/strong&gt; — only viable for self-hosted deployments where you have database access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Odoo Studio&lt;/strong&gt; — Enterprise feature that can configure webhook-style integrations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most integrations, polling with &lt;code&gt;write_date&lt;/code&gt; filtering works well enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;updated_invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using a Unified API Instead
&lt;/h2&gt;

&lt;p&gt;If you're building a product that needs to integrate with multiple accounting and ERP systems (QuickBooks, Xero, NetSuite, Sage, Microsoft Business Central), maintaining direct API integrations with each platform is a significant ongoing investment. Each one has different auth flows, data models, pagination patterns, and versioning behavior.&lt;/p&gt;

&lt;p&gt;A unified &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting API&lt;/a&gt; like Apideck normalizes these differences behind a single endpoint. You write the integration once to a standardized &lt;code&gt;Invoice&lt;/code&gt;, &lt;code&gt;Payment&lt;/code&gt;, or &lt;code&gt;Ledger Account&lt;/code&gt; model, and the platform handles the per-connector translation. This is especially useful if you're targeting vertical SaaS, embedded finance, or any segment where your customers run a mix of accounting platforms.&lt;/p&gt;

&lt;p&gt;Apideck supports &lt;a href="https://www.apideck.com/integrations/odoo" rel="noopener noreferrer"&gt;Odoo as a connector&lt;/a&gt; on both the Unified Accounting API and the CRM API, with 29+ normalized data models. On the accounting side, the connector covers invoices, bills, payments, bill payments, credit notes, journal entries, ledger accounts, tax rates, customers, suppliers, invoice items, company info, purchase orders, expenses, bank accounts, bank feed accounts, bank feed statements, subsidiaries, departments, locations, tracking categories, and attachments. The CRM side adds companies, contacts, leads, opportunities, activities, notes, and users.&lt;/p&gt;

&lt;p&gt;You can include Odoo alongside QuickBooks, Xero, and the rest without maintaining a separate direct integration, and without giving up coverage of the resources that matter for real accounting workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Odoo's API is mature, consistent, and surprisingly powerful once you get past the initial learning curve. The model-driven RPC approach means that anything you can do in the UI, you can do via API — including custom fields and modules added by your customers.&lt;/p&gt;

&lt;p&gt;The main things to keep in mind as you build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate with API keys, not passwords, in production&lt;/li&gt;
&lt;li&gt;Learn the domain filter syntax — it's the same across every model and covers complex queries&lt;/li&gt;
&lt;li&gt;Always specify &lt;code&gt;fields&lt;/code&gt; in &lt;code&gt;search_read&lt;/code&gt; to avoid pulling unnecessary data&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;write_date&lt;/code&gt; filtering for incremental sync rather than full refreshes&lt;/li&gt;
&lt;li&gt;Handle Odoo version differences explicitly, especially around the accounting type system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're integrating with Odoo as part of a broader multi-ERP strategy, it's worth evaluating whether a unified API layer saves you time versus owning each integration directly.&lt;/p&gt;

</description>
      <category>api</category>
      <category>odooapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Financial Statement APIs: What Most Accounting Platforms Won't Give You (and How to Get It Anyway)</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Wed, 01 Jul 2026 14:12:38 +0000</pubDate>
      <link>https://dev.to/apideck/financial-statement-apis-what-most-accounting-platforms-wont-give-you-and-how-to-get-it-anyway-3b7h</link>
      <guid>https://dev.to/apideck/financial-statement-apis-what-most-accounting-platforms-wont-give-you-and-how-to-get-it-anyway-3b7h</guid>
      <description>&lt;p&gt;Most accounting APIs will let you create an invoice, fetch a list of customers, or record a journal entry. But ask for a balance sheet or a trial balance, and things get complicated fast.&lt;/p&gt;

&lt;p&gt;Financial statements are the output every finance team cares about most. They're what lenders use to assess creditworthiness, what FP&amp;amp;A tools use to build forecasts, and what auditors use to verify the books. Yet the APIs behind popular accounting platforms treat financial statements as an afterthought, if they expose them at all.&lt;/p&gt;

&lt;p&gt;If you're building a SaaS product that needs financial statement data from your customers' accounting systems, you need to understand which statement types are available and how to work around the gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full picture: types of financial statements
&lt;/h2&gt;

&lt;p&gt;Before getting into API availability, it helps to be specific about what "financial statements" actually means. Five statement types matter for most integration use cases, and they vary wildly in how well accounting APIs support them.&lt;/p&gt;

&lt;p&gt;P&amp;amp;L statements and balance sheets sit at the top of the availability spectrum. Most major accounting platforms expose some form of endpoint for these two reports, though the depth and format differ. P&amp;amp;L shows revenue and expenses over a period, netting out to income or loss. Balance sheets capture a company's financial position (what it owns versus what it owes) at a specific point in time.&lt;/p&gt;

&lt;p&gt;Cash flow statements and trial balances are where things get thin. A cash flow statement reclassifies accrual-basis data into standardized cash flow categories. Xero's &lt;a href="https://developer.xero.com/documentation/api/finance/financialstatements" rel="noopener noreferrer"&gt;Finance API&lt;/a&gt; exposes a cash flow endpoint but restricts it to non-U.S. entities. QuickBooks Online has a cash flow report, but the response format is designed for HTML rendering rather than programmatic use. Many platforms simply don't offer it. Trial balances, which list every account with its debit or credit balance, face a similar problem: some platforms expose them as reports, others expect you to reconstruct them from ledger account data.&lt;/p&gt;

&lt;p&gt;The statement of changes in equity sits at the bottom of the stack. It tracks how equity changed over a period through items like net income and dividends. Very few accounting APIs expose this as a dedicated endpoint. In most cases, you'd derive it from balance sheet snapshots at two points in time.&lt;/p&gt;

&lt;p&gt;The pattern: the further you get from P&amp;amp;L and balance sheet, the thinner API support becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the gaps exist
&lt;/h2&gt;

&lt;p&gt;An accounting API that gives you access to invoices, bills, and the rest of the general ledger is giving you the raw ingredients. A financial statement is the cooked meal. The distance between those two things is significant.&lt;/p&gt;

&lt;p&gt;Financial statements require aggregation logic that varies by report type and accounting method (cash vs. accrual), plus date range and chart of accounts structure. Most accounting platforms built their APIs around CRUD operations on individual records, not around computed reports. Adding report endpoints means exposing business logic that was originally designed for the platform's own UI, and each provider has done this differently, if they've done it at all.&lt;/p&gt;

&lt;p&gt;The endpoint fragmentation becomes obvious once you look at the actual APIs. &lt;a href="https://developer.intuit.com/app/developer/qbo/docs/workflows/run-reports" rel="noopener noreferrer"&gt;QuickBooks Online&lt;/a&gt; exposes reports at a company-specific path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v3/company/&amp;lt;realmID&amp;gt;/reports/ProfitAndLoss?start_date=2025-01-01&amp;amp;end_date=2025-12-31
GET /v3/company/&amp;lt;realmID&amp;gt;/reports/BalanceSheet?date=2025-12-31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is a recursive row/column structure with headers and data cells mixed into a single nested array. It was designed for rendering HTML tables in the QuickBooks UI, not for programmatic consumption. Parsing it into a usable data structure requires custom logic for every report type.&lt;/p&gt;

&lt;p&gt;Xero splits financial statement data across two separate APIs. The standard &lt;a href="https://developer.xero.com/documentation/api/accounting/reports" rel="noopener noreferrer"&gt;Accounting API&lt;/a&gt; has report endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api.xro/2.0/Reports/ProfitAndLoss?fromDate=2025-01-01&amp;amp;toDate=2025-12-31
GET /api.xro/2.0/Reports/BalanceSheet?date=2025-12-31
GET /api.xro/2.0/Reports/TrialBalance?date=2025-12-31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Finance API (which requires separate partnership approval) exposes a different set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /finance.xro/1.0/financialstatements/profitandloss
GET /finance.xro/1.0/financialstatements/balancesheet
GET /finance.xro/1.0/financialstatements/cashflow  // not available for U.S. entities
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two APIs return different response schemas for the same data. And the Finance API's cash flow endpoint doesn't work for U.S. organizations at all.&lt;/p&gt;

&lt;p&gt;NetSuite buries financial reports behind SuiteQL or saved search configurations that require significant setup. Sage Intacct requires you to define report templates before you can pull data. Each platform has its own authentication flow and its own response schema.&lt;/p&gt;

&lt;p&gt;If you're building integrations with multiple accounting platforms, you're writing and maintaining entirely separate parsing logic for each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real use cases that depend on clean financial statement data
&lt;/h2&gt;

&lt;p&gt;The demand for financial statement APIs comes from a few distinct product categories.&lt;/p&gt;

&lt;p&gt;Lending and underwriting platforms are probably the most obvious consumer. They pull P&amp;amp;L statements and balance sheets to &lt;a href="https://www.apideck.com/use-cases/business-lending-loan-underwriting" rel="noopener noreferrer"&gt;assess creditworthiness&lt;/a&gt;. A lender evaluating a small business needs gross margin trends and current ratio computed from financial statement data. Manual document collection (requesting PDFs over email and re-keying numbers into a spreadsheet) adds days to the underwriting process. API access cuts that to minutes. Some lenders also pull trial balances for deeper account-level analysis during due diligence.&lt;/p&gt;

&lt;p&gt;Financial planning tools are another major category. &lt;a href="https://causal.app" rel="noopener noreferrer"&gt;Causal&lt;/a&gt;, a financial planning platform, integrated with customers' accounting systems to pull financial statement data into an AI-powered modeling tool. Users connect their QuickBooks or Xero account, and the platform auto-generates financial models with metrics like burn rate and runway, updated on a recurring schedule. Cash flow data is especially valuable here: forecasting tools that can ingest historical cash flow statements produce meaningfully better projections than those working from P&amp;amp;L alone.&lt;/p&gt;

&lt;p&gt;Then there's the growing segment of vertical SaaS products that &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;embed financial dashboards&lt;/a&gt; directly in their UI. A construction management platform might display a contractor's cash position alongside project data. A healthcare practice management tool might show monthly P&amp;amp;L trends next to patient volume. In both cases, the product team needs financial statement data arriving in a predictable format, on a reliable schedule, from whatever accounting system the customer happens to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it yourself vs. using a unified API
&lt;/h2&gt;

&lt;p&gt;If you only need to support one accounting platform, building a direct integration for financial statements is feasible, if tedious. You'll parse the provider's report format, map it to your internal schema, handle rate limits, and maintain it as the provider's API evolves. (For a full breakdown of &lt;a href="https://www.apideck.com/blog/the-complete-guide-to-accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;what that build process looks like&lt;/a&gt;, including authentication, sync patterns, and scaling, see the complete fintech accounting API guide.)&lt;/p&gt;

&lt;p&gt;The math changes when you need to support five or ten or twenty platforms. Each one has different authentication flows (OAuth 2.0 is standard, but implementation details vary widely) and different data models, with varying levels of financial statement support. Some platforms version their APIs aggressively; Xero has deprecated and replaced major endpoints multiple times. Others, like &lt;a href="https://www.apideck.com/blog/build-an-integration-with-quickbooks-desktop-in-2025" rel="noopener noreferrer"&gt;QuickBooks Desktop&lt;/a&gt;, still rely on SOAP-based communication through a local Web Connector.&lt;/p&gt;

&lt;p&gt;A unified API normalizes these differences behind a single interface. You build one integration, and the unified API provider handles the per-platform mapping and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;Compare the endpoint fragmentation above with &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt;, which covers the two most requested financial statement types natively. Balance Sheet and Profit and Loss endpoints work across &lt;a href="https://developers.apideck.com/apis/accounting" rel="noopener noreferrer"&gt;45+ accounting connectors&lt;/a&gt;, with a consistent schema regardless of the downstream platform. Aging reports (Aged Creditors and Aged Debtors) round out the reporting layer. Instead of writing separate parsing logic for QuickBooks' nested row structure and Xero's report schema, you write one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Apideck&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apideck/unify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_APP_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_CONSUMER_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Pull a balance sheet from any connected accounting platform&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balanceSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or 'xero', 'sage-intacct', etc.&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;periodCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;periodType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For statement types that fall outside the unified schema (trial balance, cash flow, equity changes), Apideck's Proxy feature fills the gap. The Proxy lets you make native API calls to the downstream accounting platform while still using Apideck's managed authentication and connection infrastructure. You get the benefit of not having to handle OAuth flows or token refresh for each provider, while accessing any endpoint the provider exposes, including report types that aren't part of the unified model.&lt;/p&gt;

&lt;p&gt;In practice, this means your integration architecture can use unified endpoints for P&amp;amp;L and balance sheet data (where you get normalized schemas across all connectors) and fall back to Proxy calls for provider-specific reports like trial balances or cash flow statements. You handle two code paths instead of twenty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to evaluate in a financial statement API provider
&lt;/h2&gt;

&lt;p&gt;Not every unified API covers financial statements with the same depth. When you're evaluating options, a few things matter more than they might initially seem.&lt;/p&gt;

&lt;p&gt;Connector coverage for report endpoints is the first thing to check. A provider might support 20 accounting platforms for invoices and bills but only three for balance sheets. Financial statement support is often the last thing to ship because it requires more complex mapping.&lt;/p&gt;

&lt;p&gt;Filtering and date range support also matter more than you'd expect. Can you request a P&amp;amp;L for a specific quarter? A balance sheet as of a specific date? For forecasting and trend analysis, historical snapshots are critical.&lt;/p&gt;

&lt;p&gt;Schema depth determines how useful the data actually is once it arrives. Does the unified schema preserve the chart of accounts hierarchy? Can you distinguish between current and non-current assets? Flattened schemas lose information that matters for financial analysis.&lt;/p&gt;

&lt;p&gt;Then there's data freshness. Some unified APIs cache data and sync on a schedule. Others pass requests through to the source API in real time. For financial statements that inform lending decisions, stale data is a real risk. Apideck takes the real-time approach: API calls are processed and passed directly from source to your app, with no data stored in between.&lt;/p&gt;

&lt;p&gt;Finally, check whether the provider offers a passthrough or proxy mechanism for report types outside the unified schema. If your product only needs P&amp;amp;L and balance sheets today but might need trial balances or cash flow statements tomorrow, a provider with a strong proxy layer saves you from having to build and maintain a separate direct integration later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is heading
&lt;/h2&gt;

&lt;p&gt;Financial statement data is some of the most valuable information locked inside your customers' accounting systems. Getting to it programmatically is harder than it should be, because most accounting platforms designed their APIs around transactions, not reports. And the problem gets worse the further you move beyond P&amp;amp;L and balance sheets into trial balances and cash flow reporting.&lt;/p&gt;

&lt;p&gt;You can build custom report-parsing logic for every platform and every statement type you need to support. Or you can use a unified API that covers the core statement types natively and gives you a clean fallback for everything else.&lt;/p&gt;

&lt;p&gt;If your product depends on financial statement data from customers' accounting systems, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt; gives you normalized Balance Sheet and P&amp;amp;L endpoints across 45+ platforms, plus aging reports and a Proxy layer for accessing any provider-specific report endpoint. One integration, the full reporting picture.&lt;/p&gt;

</description>
      <category>api</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
      <category>financialapi</category>
    </item>
  </channel>
</rss>
