<?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>Forem: Apideck</title>
    <description>The latest articles on Forem by Apideck (@apideck).</description>
    <link>https://forem.com/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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F10330%2F83f9e7ec-19ff-4cba-9ea1-8d42e6e4dfb3.png</url>
      <title>Forem: Apideck</title>
      <link>https://forem.com/apideck</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/apideck"/>
    <language>en</language>
    <item>
      <title>How to integrate the QuickBooks Invoice API in 2026</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 25 May 2026 22:15:04 +0000</pubDate>
      <link>https://forem.com/apideck/how-to-integrate-the-quickbooks-invoice-api-in-2026-1h8l</link>
      <guid>https://forem.com/apideck/how-to-integrate-the-quickbooks-invoice-api-in-2026-1h8l</guid>
      <description>&lt;p&gt;Building invoicing into your app sounds straightforward—until you open the QuickBooks API docs.&lt;/p&gt;

&lt;p&gt;Nested objects three levels deep. Fields named &lt;code&gt;SalesItemLineDetail&lt;/code&gt; inside &lt;code&gt;DetailType&lt;/code&gt; inside &lt;code&gt;Line&lt;/code&gt;. A &lt;code&gt;SyncToken&lt;/code&gt; you need to track for every update. Error code &lt;code&gt;5010&lt;/code&gt; when someone else touched the record 200ms before you did.&lt;/p&gt;

&lt;p&gt;The QuickBooks Invoice API is powerful, but it wasn't designed for developers who just want to create an invoice and move on.&lt;/p&gt;

&lt;p&gt;This guide covers both paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The native approach&lt;/strong&gt; — Full endpoint reference, working code examples, and the gotchas that'll save you hours of debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The unified API approach&lt;/strong&gt; — How to reduce 100+ lines of QuickBooks-specific code to 20 lines that work across QuickBooks, Xero, NetSuite, and 30+ other accounting platforms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whether you're building a direct integration or evaluating unified APIs, you'll walk away knowing exactly what QuickBooks invoicing requires under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  QuickBooks Invoice API Overview
&lt;/h2&gt;

&lt;p&gt;Before diving in, make sure you have your &lt;a href="https://www.apideck.com/blog/how-to-get-your-quickbooks-api-key" rel="noopener noreferrer"&gt;QuickBooks API credentials set up&lt;/a&gt;. If you're evaluating whether to build this integration yourself or use a unified API, check out our &lt;a href="https://www.apideck.com/blog/quickbooks-api-pricing-and-the-intuit-app-partner-program" rel="noopener noreferrer"&gt;QuickBooks API pricing breakdown&lt;/a&gt; to understand the costs involved.&lt;/p&gt;

&lt;p&gt;The QuickBooks Invoice API lets you programmatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and send invoices&lt;/li&gt;
&lt;li&gt;Add line items with products/services&lt;/li&gt;
&lt;li&gt;Apply discounts and taxes&lt;/li&gt;
&lt;li&gt;Track payment status&lt;/li&gt;
&lt;li&gt;Send payment reminders&lt;/li&gt;
&lt;li&gt;Void or delete invoices&lt;/li&gt;
&lt;li&gt;Query invoice history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Endpoints
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice/{invoiceId}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice?operation=delete&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Void&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice?operation=void&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/invoice/{invoiceId}/send&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v3/company/{realmId}/query?query=SELECT * FROM Invoice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Base URLs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production: &lt;code&gt;https://quickbooks.api.intuit.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sandbox: &lt;code&gt;https://sandbox-quickbooks.api.intuit.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Updates use the same endpoint as create. The distinction is in the request body—updates must include &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;SyncToken&lt;/code&gt; fields. Working with QuickBooks Desktop instead? See our &lt;a href="https://www.apideck.com/blog/build-an-integration-with-quickbooks-desktop-in-2025" rel="noopener noreferrer"&gt;QuickBooks Desktop integration guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating Invoices with the Native API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Invoice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&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="o"&gt;=&amp;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;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// Customer ID&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;Line&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Consulting Services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&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="s2"&gt;Strategy consulting - 3 hours&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;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-02-15&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;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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&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;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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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="nx"&gt;invoice&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoice with Multiple Line Items
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&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&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&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="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&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="s2"&gt;Development services&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&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="s2"&gt;Design services&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;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-02-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PrivateNote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal note: Rush project&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;h3&gt;
  
  
  Invoice with Discount and Tax
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceWithDiscountAndTax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&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&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;Amount&lt;/span&gt;&lt;span class="p"&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="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&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="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;TaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TAX&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="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DiscountLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DiscountLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PercentBased&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DiscountPercent&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="na"&gt;DiscountAccountRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;86&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="na"&gt;TxnTaxDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;TxnTaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;74.25&lt;/span&gt; &lt;span class="c1"&gt;// (1000 - 100) * 8.25%&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;
  
  
  Reading and Querying Invoices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Get Single Invoice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&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="o"&gt;=&amp;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;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&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;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?minorversion=75`&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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="p"&gt;}&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;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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query Examples
&lt;/h3&gt;

&lt;p&gt;QuickBooks uses a SQL-like query syntax with some limitations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported:&lt;/strong&gt; WHERE, LIKE (% wildcard only), ORDER BY, IN, AND, comparison operators, STARTPOSITION, MAXRESULTS&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not supported:&lt;/strong&gt; OR, NOT, JOIN, GROUP BY, projections (selecting specific fields)&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;// Unpaid invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM Invoice WHERE Balance &amp;gt; '0' ORDER BY DueDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Customer's invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice WHERE CustomerRef = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customerId&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;// Overdue invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&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;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice WHERE DueDate &amp;lt; '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' AND Balance &amp;gt; '0'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Date range (note: use single quotes for values)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice WHERE TxnDate &amp;gt;= '2024-01-01' AND TxnDate &amp;lt;= '2024-01-31'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// With pagination (default: 100 results, max: 1000)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice STARTPOSITION 1 MAXRESULTS 200`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Updating Invoices
&lt;/h2&gt;

&lt;p&gt;QuickBooks uses &lt;strong&gt;sparse updates&lt;/strong&gt;—send only changed fields plus &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;SyncToken&lt;/code&gt;. Updates use the same endpoint as create; the presence of &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;SyncToken&lt;/code&gt; signals an update operation:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&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;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;updatePayload&lt;/span&gt; &lt;span class="o"&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="nx"&gt;invoice&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;SyncToken&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;SyncToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Required for optimistic locking&lt;/span&gt;
    &lt;span class="na"&gt;sparse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-03-01&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;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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&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;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="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="nx"&gt;updatePayload&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Even with sparse updates, some operations may still require &lt;code&gt;CustomerRef&lt;/code&gt; and &lt;code&gt;Line&lt;/code&gt; items depending on what you're changing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sending and Managing Invoices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Send via Email
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&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="o"&gt;=&amp;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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&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;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/send?sendTo=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&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="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&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;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/send`&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="nx"&gt;url&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;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="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/octet-stream&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auto-Send on Creation
&lt;/h3&gt;

&lt;p&gt;To automatically email an invoice when creating it, set &lt;code&gt;EmailStatus&lt;/code&gt; to &lt;code&gt;NeedToSend&lt;/code&gt;. Note that &lt;code&gt;BillEmail&lt;/code&gt; is required when using this option:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;BillEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Required when EmailStatus is NeedToSend&lt;/span&gt;
  &lt;span class="na"&gt;EmailStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NeedToSend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Options: NotSet, NeedToSend, EmailSent&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&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;
  
  
  Void an Invoice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;voidInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&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;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?operation=void`&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;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="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;Id&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;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SyncToken&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;SyncToken&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reconciling Payments
&lt;/h2&gt;

&lt;p&gt;Once invoices are sent, you'll need to record payments against them. QuickBooks uses a separate &lt;a href="https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/payment" rel="noopener noreferrer"&gt;Payment API&lt;/a&gt; that links payments to invoices through the &lt;code&gt;LinkedTxn&lt;/code&gt; object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Record a Full Payment
&lt;/h3&gt;

&lt;p&gt;To record a payment against an invoice, create a Payment object that references the invoice:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&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="o"&gt;=&amp;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;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Line&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&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;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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;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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&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;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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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="nx"&gt;payment&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;QuickBooks supports partial payments—just set the &lt;code&gt;Amount&lt;/code&gt; to less than the invoice total:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordPartialPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&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="o"&gt;=&amp;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;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Line&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&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;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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;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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&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;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="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="nx"&gt;payment&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After recording a partial payment, the invoice's &lt;code&gt;Balance&lt;/code&gt; field will reflect the remaining amount due.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Against Multiple Invoices
&lt;/h3&gt;

&lt;p&gt;A single payment can be applied across multiple invoices:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Line&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;Amount&lt;/span&gt;&lt;span class="p"&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="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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;
  
  
  Apply Credits to Invoices
&lt;/h3&gt;

&lt;p&gt;If a customer has credits (from overpayments or credit memos), you can apply them to invoices:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applyCredit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;creditMemoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&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="o"&gt;=&amp;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;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&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="c1"&gt;// Zero when applying credits only&lt;/span&gt;
    &lt;span class="na"&gt;Line&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;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creditMemoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CreditMemo&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="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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&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;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="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="nx"&gt;payment&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="k"&gt;return&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query Payment Status
&lt;/h3&gt;

&lt;p&gt;Check if an invoice has been paid by querying its &lt;code&gt;Balance&lt;/code&gt; field or by finding linked payments:&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;// Find all payments for an invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Payment WHERE Line.LinkedTxn.TxnId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&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;// Or check invoice balance directly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&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;getInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPaid&lt;/span&gt; &lt;span class="o"&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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;isPartiallyPaid&lt;/span&gt; &lt;span class="o"&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Apideck
&lt;/h3&gt;

&lt;p&gt;Apideck normalizes payment recording across all 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="c1"&gt;// Record a payment&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;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;consumerId&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;payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&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;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transaction_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;allocations&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;invoice&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;invoiceId&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;500.00&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="c1"&gt;// List payments for a customer&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;payments&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;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;consumerId&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;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;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&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;
  
  
  Before &amp;amp; After: Native QuickBooks vs. Apideck
&lt;/h2&gt;

&lt;p&gt;Here's where developer experience really matters. Building invoicing with the native API requires understanding QuickBooks-specific concepts. With Apideck, you use a clean, normalized model that works across &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;QuickBooks, Xero, NetSuite, and 30+ other accounting platforms&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Native QuickBooks Invoice Creation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The full picture of what you need to manage&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuickBooksInvoiceService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&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;QuickBooksAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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="c1"&gt;// 1. Handle authentication&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&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;realmId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRealmId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Transform your data to QuickBooks format&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerData&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;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// QuickBooks requires Amount at line level&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Must specify DetailType exactly&lt;/span&gt;
        &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// ItemRef is required and must exist in QuickBooks&lt;/span&gt;
          &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productName&lt;/span&gt; &lt;span class="c1"&gt;// Optional but helps debugging&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// TaxCodeRef uses QuickBooks-specific codes&lt;/span&gt;
          &lt;span class="na"&gt;TaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TAX&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NON&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;Description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LineNum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;index&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="c1"&gt;// Date format must be YYYY-MM-DD&lt;/span&gt;
      &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;TxnDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceDate&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;Date&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
      &lt;span class="c1"&gt;// QuickBooks calls this DocNumber (max 21 characters)&lt;/span&gt;
      &lt;span class="na"&gt;DocNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Private note vs customer memo are different fields&lt;/span&gt;
      &lt;span class="na"&gt;PrivateNote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internalMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Internal only, up to 4000 chars&lt;/span&gt;
      &lt;span class="na"&gt;CustomerMemo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerNote&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerNote&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Email handling - BillEmail required if EmailStatus is NeedToSend&lt;/span&gt;
      &lt;span class="na"&gt;BillEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;EmailStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendImmediately&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NeedToSend&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="s2"&gt;NotSet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Handle tax if applicable&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TxnTaxDetail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TxnTaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxCodeId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;TotalTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculateTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&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;// 4. Make the API call&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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&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;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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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="nx"&gt;qbInvoice&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;// 5. Handle QuickBooks-specific errors&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;ok&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;error&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qbError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fault&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nb"&gt;Error&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="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qbError&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6140&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Duplicate document number: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceNumber&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6240&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Duplicate name exists (customer/vendor/employee)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;610&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&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="nx"&gt;customerData&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;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid item reference in line items`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5010&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stale data - invoice was modified. Refetch and retry.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`QuickBooks error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;qbError&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&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;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;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="c1"&gt;// 6. Send email if requested (already handled via EmailStatus, but can also call send endpoint)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendImmediately&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EmailStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendInvoice&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;Invoice&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="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 7. Transform response back to your format&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformFromQuickBooks&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;Invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&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;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&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;result&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&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="nf"&gt;setDate&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="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;days&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;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;calculateTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&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;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;transformFromQuickBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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="nx"&gt;qbInvoice&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;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;paid&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&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&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="na"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MetaData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// ... more field mapping&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;&lt;strong&gt;Pain points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100+ lines for basic invoice creation&lt;/li&gt;
&lt;li&gt;QuickBooks-specific field names (&lt;code&gt;DetailType&lt;/code&gt;, &lt;code&gt;SalesItemLineDetail&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Manual date formatting&lt;/li&gt;
&lt;li&gt;Complex nested object structure&lt;/li&gt;
&lt;li&gt;Platform-specific error codes to handle&lt;/li&gt;
&lt;li&gt;Transform data in and out&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After: With Apideck
&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="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="c1"&gt;// Create invoice - clean, intuitive API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&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;// Switch to 'xero' or 'netsuite' anytime&lt;/span&gt;
    &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invoice_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invoice_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&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="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memo&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="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tax_rate_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRateId&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="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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// List invoices with filters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUnpaidInvoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&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;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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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="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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Get single invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Update invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updates&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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Delete invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&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;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&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;
  
  
  Code Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Native QuickBooks&lt;/th&gt;
&lt;th&gt;Apideck&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth handling&lt;/td&gt;
&lt;td&gt;Manual (50+ lines)&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data transformation&lt;/td&gt;
&lt;td&gt;Required (both ways)&lt;/td&gt;
&lt;td&gt;None needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Platform-specific codes&lt;/td&gt;
&lt;td&gt;Unified errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-platform&lt;/td&gt;
&lt;td&gt;Complete rewrite&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;serviceId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;QuickBooks docs&lt;/td&gt;
&lt;td&gt;One &lt;a href="https://dev.to/blog/what-is-a-unified-api"&gt;unified API&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Real-World Example: CRM to Invoice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Native approach:&lt;/strong&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="c1"&gt;// When deal closes, create invoice - native QuickBooks&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onDealWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&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;qbAuth&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;QuickBooksAuth&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;accessToken&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;qbAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&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;realmId&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;qbAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRealmId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// First, find or create customer in QuickBooks&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;customer&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;findQuickBooksCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;customer&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;customerResponse&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://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/customer?minorversion=75`&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;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="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;DisplayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&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="na"&gt;PrimaryEmailAddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;CompanyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;customerResponse&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;Customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Then create the invoice&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&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;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mapProductToQBItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;30&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;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&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;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="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="nx"&gt;invoice&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;// Handle errors, send email, etc...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Apideck:&lt;/strong&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="c1"&gt;// When deal closes, create invoice - with Apideck&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onDealWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create customer if needed&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;customer&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;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="nx"&gt;deal&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="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;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&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="na"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&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="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If customer exists, find them&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;)&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;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;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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="nx"&gt;deal&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="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;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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Create invoice&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;invoice&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;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="nx"&gt;deal&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="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;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&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;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;p&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="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
      &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;invoice&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;
  
  
  Common Error Handling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  QuickBooks Error Codes Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error Code&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6000&lt;/td&gt;
&lt;td&gt;Business Validation Error&lt;/td&gt;
&lt;td&gt;Check required fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6140&lt;/td&gt;
&lt;td&gt;Duplicate Document Number&lt;/td&gt;
&lt;td&gt;Use unique DocNumber&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6240&lt;/td&gt;
&lt;td&gt;Duplicate Name Exists&lt;/td&gt;
&lt;td&gt;Customer/vendor/employee name already exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;610&lt;/td&gt;
&lt;td&gt;Object Not Found&lt;/td&gt;
&lt;td&gt;Customer or Item invalid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5010&lt;/td&gt;
&lt;td&gt;Stale Object&lt;/td&gt;
&lt;td&gt;Refresh SyncToken and retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2500&lt;/td&gt;
&lt;td&gt;Invalid Reference&lt;/td&gt;
&lt;td&gt;Referenced entity doesn't exist&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  With Apideck
&lt;/h3&gt;

&lt;p&gt;Errors are normalized across all 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;try&lt;/span&gt; &lt;span class="p"&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;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validation error - check error.detail&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Customer or item not found&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Duplicate invoice number&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;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Choosing between native integration and a unified API? Consider your long-term roadmap. If you'll need &lt;a href="https://dev.to/blog/accounting-erp-partnership-guide"&gt;multiple accounting integrations&lt;/a&gt;, the unified approach saves significant engineering time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native QuickBooks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Register at developer.intuit.com&lt;/li&gt;
&lt;li&gt;Create an app&lt;/li&gt;
&lt;li&gt;Implement OAuth flow&lt;/li&gt;
&lt;li&gt;Learn QuickBooks data model&lt;/li&gt;
&lt;li&gt;Build integration&lt;/li&gt;
&lt;li&gt;Handle edge cases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Timeline: 2-4 weeks&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  With Apideck
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at apideck.com&lt;/li&gt;
&lt;li&gt;Get API key&lt;/li&gt;
&lt;li&gt;Use Vault for OAuth&lt;/li&gt;
&lt;li&gt;Start making API calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Timeline: 1 day&lt;/strong&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="c1"&gt;// You're ready to go&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="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appId&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="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="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;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customer_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;cust-456&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="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;Services&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&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="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invoice created:&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.apideck.com/products/accounting-api" rel="noopener noreferrer"&gt;Start building with Apideck →&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;QuickBooks guides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/how-to-get-your-quickbooks-api-key" rel="noopener noreferrer"&gt;How to Get Your QuickBooks API Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/quickbooks-api-pricing-and-the-intuit-app-partner-program" rel="noopener noreferrer"&gt;QuickBooks API Pricing and the Intuit App Partner Program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/build-an-integration-with-quickbooks-desktop-in-2025" rel="noopener noreferrer"&gt;Build an Integration with QuickBooks Desktop in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/accounts-payable-automation-quickbooks" rel="noopener noreferrer"&gt;Accounts Payable Automation with QuickBooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Accounting integrations:&lt;/strong&gt;&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&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&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;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/netsuite-integration-guide" rel="noopener noreferrer"&gt;NetSuite Integration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;Accounting API Integration for Fintech&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Platform comparisons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/what-is-a-unified-api" rel="noopener noreferrer"&gt;What is a Unified API?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/rutter-alternatives" rel="noopener noreferrer"&gt;Rutter Alternatives: A Technical Evaluation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>quickbooks</category>
      <category>accountingsoftware</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Understanding Tracking Dimensions in Accounting Integrations</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Thu, 21 May 2026 15:43:14 +0000</pubDate>
      <link>https://forem.com/apideck/understanding-tracking-dimensions-in-accounting-integrations-2hj0</link>
      <guid>https://forem.com/apideck/understanding-tracking-dimensions-in-accounting-integrations-2hj0</guid>
      <description>&lt;p&gt;If you're building &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting integrations&lt;/a&gt;, you've likely encountered a common challenge: your customers want to segment their financial data by department, location, project, or business unit—but every accounting platform handles this differently.&lt;/p&gt;

&lt;p&gt;QuickBooks calls them "Classes." Xero uses "Tracking Categories." NetSuite has departments, locations, subsidiaries, AND classes. Intuit's new Enterprise Suite supports up to 20 custom dimensions. Sage Intacct offers eight.&lt;/p&gt;

&lt;p&gt;In this post, we'll break down how tracking dimensions work across major accounting platforms and show you how to build integrations that handle these differences gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Tracking Dimensions?
&lt;/h2&gt;

&lt;p&gt;Tracking dimensions are metadata tags attached to accounting transactions that enable segmented reporting. Think of them as labels that let finance teams answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What's our P&amp;amp;L for the Marketing department?"&lt;/li&gt;
&lt;li&gt;"How much revenue did the New York office generate?"&lt;/li&gt;
&lt;li&gt;"What are the expenses for Project Alpha?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most accounting platforms support some combination of four dimension types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Department&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Organizational unit or cost center&lt;/td&gt;
&lt;td&gt;Marketing, Sales, Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Physical or logical place&lt;/td&gt;
&lt;td&gt;NYC Office, London, Online Store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subsidiary&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Legal entity in multi-company setup&lt;/td&gt;
&lt;td&gt;US Holdings Inc, UK Ltd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracking Category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flexible custom tags&lt;/td&gt;
&lt;td&gt;Project Alpha, Grant #12345&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Challenge: Every Platform Is Different
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Each accounting platform implements tracking dimensions differently:&lt;/p&gt;

&lt;h3&gt;
  
  
  Xero: Keep It Simple (Maybe Too Simple)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt; takes a minimalist approach—you get exactly &lt;strong&gt;two tracking categories&lt;/strong&gt; per organization. That's it. No departments, no locations, no subsidiaries.&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;"tracking_categories"&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="s2"&gt;"region-uuid"&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;"North America"&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="s2"&gt;"dept-uuid"&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;"Sales"&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;Many Xero users work around this by creating one tracking category for "Department" and another for "Region." It works, but requires careful planning since you can't add a third dimension later.&lt;/p&gt;

&lt;h3&gt;
  
  
  QuickBooks Online: The Naming Confusion
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt; supports departments, locations, AND classes—but there's a catch. In QuickBooks, "Department" often functions more like what other systems call "Location" (tracking by branch or region).&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;"department_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;"58"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_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;"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;"tracking_categories"&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="s2"&gt;"5000000000000026437"&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;"Enterprise"&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;Also, these features aren't enabled by default. Your customers need to turn on "Track locations" and "Track classes" in their QuickBooks settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  NetSuite OneWorld: All The Dimensions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; goes all-in with full support for departments, locations, subsidiaries, and classes. The tradeoff? Complexity.&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;"department_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;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_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;"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;"subsidiary_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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&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="s2"&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;"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;"Class A"&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 NetSuite OneWorld (multi-subsidiary) accounts, &lt;code&gt;subsidiary_id&lt;/code&gt; is often &lt;strong&gt;required&lt;/strong&gt;. Transactions without a valid subsidiary get rejected. Your integration needs to handle this gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intuit Enterprise Suite: The Modern Approach
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/intuit-enterprise-suite" rel="noopener noreferrer"&gt;Intuit Enterprise Suite&lt;/a&gt; (IES) is Intuit's new mid-market offering, and it takes a fundamentally different approach than QuickBooks. Instead of fixed dimension types, IES supports &lt;strong&gt;up to 20 custom dimensions&lt;/strong&gt; with unlimited values and 5 levels of hierarchy.&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;"dimensions"&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;"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;"Department"&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="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;"dept-001"&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;"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;"Location"&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="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;"loc-001"&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;"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;"Project"&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="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;"proj-001"&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;"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;"CostCenter"&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="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;"cc-001"&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;&lt;strong&gt;IES vs QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;QuickBooks Online&lt;/th&gt;
&lt;th&gt;Intuit Enterprise Suite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom dimensions&lt;/td&gt;
&lt;td&gt;3 fixed&lt;/td&gt;
&lt;td&gt;Up to 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy depth&lt;/td&gt;
&lt;td&gt;2 levels&lt;/td&gt;
&lt;td&gt;5 levels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-entity&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (native)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared dimensions&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Across entities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your customers are outgrowing QuickBooks, IES is often the next step—and your integration needs to handle the richer dimension model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sage Intacct: Maximum Flexibility
&lt;/h3&gt;

&lt;p&gt;Sage Intacct supports up to eight custom dimensions plus the standard ones. Great for complex enterprise requirements, but it means more edge cases to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Integrations That Handle The Differences
&lt;/h2&gt;

&lt;p&gt;Here's our recommended approach for building robust accounting integrations:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Always Discover Dimensions First
&lt;/h3&gt;

&lt;p&gt;Before writing transactions with tracking data, query what dimensions are available:&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;// Fetch available dimensions for the connected account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;departments&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;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;departments&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locations&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;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locations&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackingCategories&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;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackingCategories&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which dimensions exist in this account&lt;/li&gt;
&lt;li&gt;What valid IDs you can use&lt;/li&gt;
&lt;li&gt;How the customer has configured their tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Handle Missing Support Gracefully
&lt;/h3&gt;

&lt;p&gt;Not every connector supports every dimension. Your code should check before including fields:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&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="nx"&gt;lineItems&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Only include tracking if supported&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;connectorSupports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;departments&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;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;departmentId&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="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;connectorSupports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tracking_categories&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;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracking_categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;categories&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 connector-specific fields that aren't in the unified model, you can use &lt;a href="https://developers.apideck.com/guides/pass-through" rel="noopener noreferrer"&gt;pass_through&lt;/a&gt; to send them directly to the downstream API.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use Line Items for Mixed Tracking
&lt;/h3&gt;

&lt;p&gt;Real-world invoices often span multiple departments or projects. Most platforms support tracking at the line item level:&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;"customer_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;"cust-123"&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_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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sales consulting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"department_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;"sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&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;"project-a"&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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Technical implementation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"department_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;"engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&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;"project-a"&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;Line item values override header values, giving you granular control.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Validate Subsidiary Requirements
&lt;/h3&gt;

&lt;p&gt;For enterprise platforms like NetSuite and Sage Intacct, check if subsidiary is required:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;netsuite&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subsidiaries&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;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subsidiaries&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subsidiaries&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subsidiary_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&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_id is required for multi-subsidiary accounts&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;h3&gt;
  
  
  5. Cache Dimension Lists
&lt;/h3&gt;

&lt;p&gt;Tracking dimensions change infrequently. Cache them to avoid unnecessary API calls:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;departments&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`departments:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;consumerId&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;serviceId&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="o"&gt;=&amp;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;departments&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Reference: Connector Support Matrix
&lt;/h2&gt;

&lt;p&gt;Here's what each major platform supports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Connector&lt;/th&gt;
&lt;th&gt;Departments&lt;/th&gt;
&lt;th&gt;Locations&lt;/th&gt;
&lt;th&gt;Subsidiaries&lt;/th&gt;
&lt;th&gt;Tracking Categories&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NetSuite&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sage Intacct&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intuit Enterprise Suite&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅ (up to 20)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuickBooks Online&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workday&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamics 365 BC&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Xero&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MYOB&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For complete coverage details, see our &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Accounting API page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Invalid department_id" errors&lt;/strong&gt;: Always verify IDs exist by listing dimensions first. Departments can be archived or restricted to certain subsidiaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Xero's two-category limit&lt;/strong&gt;: This is organization-wide, not per-transaction. Help customers plan their category usage upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks features not enabled&lt;/strong&gt;: If tracking fails, check whether the customer has enabled Classes and Locations in their QuickBooks settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NetSuite subsidiary requirements&lt;/strong&gt;: For OneWorld accounts, subsidiary is usually mandatory. Query subsidiaries and require selection if multiple exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Tracking dimensions are essential for meaningful financial reporting, but the implementation varies wildly across platforms. The key is building integrations that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discover available dimensions dynamically&lt;/li&gt;
&lt;li&gt;Handle missing support gracefully&lt;/li&gt;
&lt;li&gt;Validate requirements before submitting transactions&lt;/li&gt;
&lt;li&gt;Cache dimension lists for performance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the complete technical reference with curl examples and API details, see our &lt;a href="https://developers.apideck.com/guides/locations-subsidiaries-departments" rel="noopener noreferrer"&gt;Tracking Dimensions Developer Guide&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building accounting integrations? &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Unified Accounting API&lt;/a&gt; normalizes tracking dimensions across 25+ connectors, so you can write code once and support them all.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>accounting</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>The Complete Guide to Exact Online API Integration in 2026</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Sat, 09 May 2026 21:30:46 +0000</pubDate>
      <link>https://forem.com/apideck/the-complete-guide-to-exact-online-api-integration-in-2026-529c</link>
      <guid>https://forem.com/apideck/the-complete-guide-to-exact-online-api-integration-in-2026-529c</guid>
      <description>&lt;p&gt;Exact Online is the leading cloud-based accounting software in the Netherlands and Belgium, with growing adoption across the UK, Germany, and other European markets. For developers building financial integrations, vertical SaaS products, or multi-tenant applications, the Exact Online API is essential—but it comes with unique challenges that trip up even experienced teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How Exact Online's OAuth 2.0 flow works (and its unusual requirements)&lt;/li&gt;
&lt;li&gt;REST API structure with OData filtering&lt;/li&gt;
&lt;li&gt;Rate limiting strategies and how to handle them&lt;/li&gt;
&lt;li&gt;Multi-division architecture and why it matters&lt;/li&gt;
&lt;li&gt;Working code examples in Python and JavaScript&lt;/li&gt;
&lt;li&gt;How to reduce integration complexity by 10x with unified APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding the Exact Online API Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two APIs, Different Purposes
&lt;/h3&gt;

&lt;p&gt;Exact Online provides two distinct APIs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API Type&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OData-based REST&lt;/td&gt;
&lt;td&gt;Primary integration method&lt;/td&gt;
&lt;td&gt;60 records per request (bulk: 1000)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;XML API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SOAP/XML&lt;/td&gt;
&lt;td&gt;Legacy operations, specific actions&lt;/td&gt;
&lt;td&gt;Being phased out, limited support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For new integrations, always use the REST API. The XML API exists primarily for edge cases not yet supported by REST—like programmatic invoice-payment matching (reconciliation), which has no REST endpoint. Note that XML reconciliation requires manual file upload through the Exact Online web interface rather than true API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Division Architecture
&lt;/h3&gt;

&lt;p&gt;Unlike most accounting software, Exact Online uses a &lt;strong&gt;division-based&lt;/strong&gt; architecture. Every API call requires a division ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/v1/{division}/salesinvoice/SalesInvoices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single Exact Online account can contain multiple divisions (essentially separate accounting environments). Before making any API call, you must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate the user&lt;/li&gt;
&lt;li&gt;Fetch their available divisions via &lt;code&gt;/api/v1/current/Me&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Let them select which division to work with&lt;/li&gt;
&lt;li&gt;Include that division ID in all subsequent requests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This catches many developers off guard—you can't simply authenticate and start fetching invoices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: OAuth 2.0 with Exact Online
&lt;/h2&gt;

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

&lt;p&gt;Before you can authenticate users, register your app in the &lt;a href="https://apps.exactonline.com" rel="noopener noreferrer"&gt;Exact Online App Center&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Manage &amp;gt; Apps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create a new app (choose Public or Private)&lt;/li&gt;
&lt;li&gt;Note your &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client Secret&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Configure your &lt;strong&gt;Callback URL&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Critical limitation&lt;/strong&gt;: Until your app passes Exact's internal review, it can only connect with users from the &lt;strong&gt;same Exact instance&lt;/strong&gt; that created it. This means you cannot onboard pilot customers from other tenants until approval—a significant blocker for startups testing with beta users.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth 2.0 Endpoints
&lt;/h3&gt;

&lt;p&gt;Exact Online uses regional endpoints. Here are the primary ones:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Auth URL&lt;/th&gt;
&lt;th&gt;Token URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Netherlands&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.nl/api/oauth2/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.nl/api/oauth2/token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Belgium&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.be/api/oauth2/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.be/api/oauth2/token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UK&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.co.uk/api/oauth2/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.co.uk/api/oauth2/token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.de/api/oauth2/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://start.exactonline.de/api/oauth2/token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Native OAuth Implementation
&lt;/h3&gt;

&lt;p&gt;Here's a complete OAuth 2.0 implementation in Python:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExactOnlineAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirect_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redirect_uri&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&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;https://start.exactonline.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_authorization_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generate the OAuth authorization URL.&lt;/span&gt;&lt;span class="sh"&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;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redirect_uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_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;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;force_login&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="c1"&gt;# Set to 1 to force re-authentication
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&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;self&lt;/span&gt;&lt;span class="p"&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/oauth2/auth?&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exchange_code_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authorization_code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Exchange authorization code for access token.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;token_url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Exact Online requires Basic auth header for token exchange
&lt;/span&gt;        &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&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="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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_secret&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="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&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="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;Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;credentials&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/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;data&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;grant_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;authorization_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;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;authorization_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redirect_uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirect_uri&lt;/span&gt;
        &lt;span class="p"&gt;}&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_url&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;data&lt;/span&gt;&lt;span class="o"&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;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Token exchange failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Refresh the access token using the refresh token.&lt;/span&gt;&lt;span class="sh"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refresh_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No refresh token available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;token_url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&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="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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_secret&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="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&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="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;Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;credentials&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/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;data&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;grant_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;refresh_token&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;refresh_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;refresh_token&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;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="n"&gt;token_url&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;data&lt;/span&gt;&lt;span class="o"&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;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Token refresh failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_current_division&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s current division.&lt;/span&gt;&lt;span class="sh"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not authenticated&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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;Accept&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="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;self&lt;/span&gt;&lt;span class="p"&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/v1/current/Me&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Failed to fetch user info: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="n"&gt;user_data&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d&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;results&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;CurrentDivision&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;That's about 100 lines just for authentication. And you still need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token storage and encryption&lt;/li&gt;
&lt;li&gt;Automatic token refresh before expiry&lt;/li&gt;
&lt;li&gt;Multi-region endpoint routing&lt;/li&gt;
&lt;li&gt;Division selection UI&lt;/li&gt;
&lt;li&gt;Error handling for revoked tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Working with the REST API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  OData Query Parameters
&lt;/h3&gt;

&lt;p&gt;Exact Online's REST API is OData-based, supporting these parameters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$select&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Choose specific fields&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$select=InvoiceID,InvoiceNumber,AmountDC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$filter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filter results&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$filter=AmountDC gt 1000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$orderby&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sort results&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$orderby=InvoiceDate desc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$top&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Limit results (max 60, bulk: 1000)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$top=60&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$skip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pagination offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$skip=60&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Unlike many OData implementations, Exact Online does &lt;strong&gt;not&lt;/strong&gt; support the &lt;code&gt;$expand&lt;/code&gt; parameter. Related entities (such as invoice line items) must be fetched through separate API calls rather than expanded inline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching Sales Invoices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExactOnlineClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;division_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;division_id&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&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;auth&lt;/span&gt;&lt;span class="p"&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/v1/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;division_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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;Accept&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;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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter_expr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch sales invoices with OData parameters.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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;/salesinvoice/SalesInvoices&lt;/span&gt;&lt;span class="sh"&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;$top&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$skip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;skip&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;InvoiceID,InvoiceNumber,InvoiceTo,InvoiceDate,AmountDC,Currency,Status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filter_expr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;params&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter_expr&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$orderby&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_by&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="n"&gt;url&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_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="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Rate limited - check headers for retry info
&lt;/span&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limit exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="n"&gt;data&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d&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;results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_invoice_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch a single invoice by GUID.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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;/salesinvoice/SalesInvoices(guid&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;url&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_headers&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="k"&gt;return&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_invoice_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch invoice lines separately (no $expand support).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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;/salesinvoice/SalesInvoiceLines&lt;/span&gt;&lt;span class="sh"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InvoiceID eq guid&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'"&lt;/span&gt;
        &lt;span class="p"&gt;}&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="n"&gt;url&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_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="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="k"&gt;return&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d&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;results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice_data&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 a new sales invoice.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;self&lt;/span&gt;&lt;span class="p"&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;/salesinvoice/SalesInvoices&lt;/span&gt;&lt;span class="sh"&gt;"&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;url&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_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="n"&gt;invoice_data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Failed to create invoice: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="k"&gt;return&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_all_invoices_paginated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter_expr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch all invoices with automatic pagination.&lt;/span&gt;&lt;span class="sh"&gt;"""&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="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter_expr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filter_expr&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="c1"&gt;# Exact Online returns exactly $top results if more exist
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&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;skip&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;60&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;h3&gt;
  
  
  Creating an Invoice with Line Items
&lt;/h3&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;create_complete_invoice&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;customer_id&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 an invoice with line items.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;invoice_data&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;InvoiceTo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# GUID of the customer account
&lt;/span&gt;        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OrderDate&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-01-30&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;Description&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;Professional Services - January 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;PaymentCondition&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;30&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Net 30 payment terms
&lt;/span&gt;        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Currency&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;EUR&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;SalesInvoiceLines&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;Item&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;a1b2c3d4-e5f6-7890-abcd-ef1234567890&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Item GUID
&lt;/span&gt;                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Description&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;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UnitPrice&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.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;VATCode&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;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# Standard VAT
&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;Item&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;b2c3d4e5-f6a7-8901-bcde-f12345678901&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;Description&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;Implementation Support&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;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UnitPrice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;125.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;VATCode&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;1&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rate Limits: Understanding the Constraints
&lt;/h2&gt;

&lt;p&gt;Exact Online applies rate limiting at two levels—per minute and per day—for each app-company combination:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit Type&lt;/th&gt;
&lt;th&gt;Threshold&lt;/th&gt;
&lt;th&gt;Reset&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per Minute&lt;/td&gt;
&lt;td&gt;60 calls&lt;/td&gt;
&lt;td&gt;Resets every minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per Day&lt;/td&gt;
&lt;td&gt;5,000 calls&lt;/td&gt;
&lt;td&gt;Resets at midnight (company timezone)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: While some partners may negotiate higher limits for specific use cases, there is no publicly documented tiered pricing structure. Plan your integration around the standard limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limit Headers
&lt;/h3&gt;

&lt;p&gt;When you hit a limit, you'll receive HTTP 429. Exact Online uses a dual-header system for minutely and daily limits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minutely limit headers&lt;/strong&gt; (sent when minutely limit is exhausted):&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-RateLimit-Minutely-Remaining: 0
X-RateLimit-Minutely-Reset: 1706648460
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Daily limit headers&lt;/strong&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;X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706684400
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: When you have no more minutely calls available, Exact only sends the minutely headers. Your error handling should check for both header variants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate Limit Handler:&lt;/strong&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
        &lt;span class="n"&gt;self&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="c1"&gt;# Check for minutely-specific headers first
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutely_remaining&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="nf"&gt;get&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-RateLimit-Minutely-Remaining&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutely_reset&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="nf"&gt;get&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-RateLimit-Minutely-Reset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Fall back to daily headers
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;daily_remaining&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="nf"&gt;get&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-RateLimit-Remaining&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;daily_reset&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="nf"&gt;get&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-RateLimit-Reset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_minutely_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutely_remaining&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_minutely_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutely_reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutely_reset&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;daily_reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;daily_reset&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimitedClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Execute API call with automatic rate limit handling.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;RateLimitError&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="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_minutely_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Daily limit - can't retry today
&lt;/span&gt;                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Daily rate limit exceeded. &lt;/span&gt;&lt;span class="sh"&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;Resets at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ctime&lt;/span&gt;&lt;span class="p"&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;reset_time&lt;/span&gt;&lt;span class="p"&gt;)&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="c1"&gt;# Minutely limit - wait and retry
&lt;/span&gt;                &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&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;reset_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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;Minutely rate limited. Waiting &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max retries exceeded&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;h2&gt;
  
  
  Common Pitfalls and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Reconciliation Problem
&lt;/h3&gt;

&lt;p&gt;One of the most requested features—automatically matching payments to invoices—has &lt;strong&gt;no REST API endpoint&lt;/strong&gt;. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create invoices via API&lt;/li&gt;
&lt;li&gt;Create payments via API&lt;/li&gt;
&lt;li&gt;But you &lt;strong&gt;cannot&lt;/strong&gt; programmatically reconcile them via API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only workaround is uploading XML files with matching information through the Exact Online web interface—not through an API call. This requires manual intervention and breaks many automated accounting workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No $expand Support
&lt;/h3&gt;

&lt;p&gt;Unlike standard OData implementations, Exact Online does not support &lt;code&gt;$expand&lt;/code&gt;. This means you cannot retrieve an invoice with its line items in a single request. Instead, you must:&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;# Fetch invoice
&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_invoice_by_id&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="c1"&gt;# Fetch line items separately
&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_invoice_lines&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;p&gt;This doubles your API calls for any operation requiring related data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Division Selection is Mandatory
&lt;/h3&gt;

&lt;p&gt;Every request needs the division ID. If you hardcode it, your integration breaks when users switch divisions or have multiple. Always:&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;# On initial connection
&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user_divisions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;selected_division&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prompt_user_to_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;store_division_preference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selected_division&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. App Approval Bottleneck
&lt;/h3&gt;

&lt;p&gt;Your app won't work with external customers until Exact approves it. This process can take weeks, during which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can only test with your own instance&lt;/li&gt;
&lt;li&gt;Beta customers can't connect&lt;/li&gt;
&lt;li&gt;You're blocked on external validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plan for this in your launch timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Regional Endpoint Complexity
&lt;/h3&gt;

&lt;p&gt;Unlike most APIs with a single global endpoint, Exact Online requires you to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Know which region the customer is in&lt;/li&gt;
&lt;li&gt;Route to the correct regional endpoint&lt;/li&gt;
&lt;li&gt;Handle token refresh per region
&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="n"&gt;REGIONS&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;nl&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;https://start.exactonline.nl&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;be&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;https://start.exactonline.be&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;uk&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;https://start.exactonline.co.uk&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;de&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;https://start.exactonline.de&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;us&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;https://start.exactonline.com&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;es&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;https://start.exactonline.es&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;fr&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;https://start.exactonline.fr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_client_for_region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REGIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;Unsupported region: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ExactOnlineAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Simpler Path: Unified Accounting APIs
&lt;/h2&gt;

&lt;p&gt;Building and maintaining direct Exact Online integrations requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~500 lines of authentication and API client code&lt;/li&gt;
&lt;li&gt;Rate limit handling with dual header support&lt;/li&gt;
&lt;li&gt;Multi-region endpoint management&lt;/li&gt;
&lt;li&gt;Division selection UI&lt;/li&gt;
&lt;li&gt;Separate calls for related entities (no $expand)&lt;/li&gt;
&lt;li&gt;Ongoing maintenance as Exact updates their API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams building multi-platform accounting integrations, this complexity multiplies. Add Xero, QuickBooks, Sage, and FreshBooks, and you're maintaining 5 separate integrations with different auth flows, data models, and quirks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Native Exact Online Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Authentication setup
&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExactOnlineAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EXACT_CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EXACT_CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;redirect_uri&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://app.example.com/callback&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# OAuth flow
&lt;/span&gt;&lt;span class="n"&gt;auth_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_authorization_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... redirect user, handle callback ...
&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exchange_code_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Get division
&lt;/span&gt;&lt;span class="n"&gt;division_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_current_division&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Create client
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExactOnlineClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Fetch invoices with pagination and rate limiting
&lt;/span&gt;&lt;span class="n"&gt;rate_limited_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RateLimitedClient&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;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rate_limited_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_with_retry&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;get_all_invoices_paginated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filter_expr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InvoiceDate gt datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-01-01&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Fetch line items separately for each invoice (no $expand)
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;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;lines&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_invoice_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;InvoiceID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Transform to your data model
&lt;/span&gt;&lt;span class="n"&gt;normalized_invoices&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="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="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InvoiceID&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;number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InvoiceNumber&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_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InvoiceTo&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="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AmountDC&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;currency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Currency&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;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;map_exact_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Status&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="nf"&gt;parse_odata_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;InvoiceDate&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;line_items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lines&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;for&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&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;&lt;strong&gt;Total: ~600 lines of code&lt;/strong&gt; across authentication, client, rate limiting, and data transformation.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: Apideck Unified API
&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;apideck&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;app_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;consumer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user-123&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Fetch invoices - works identically for Exact Online, Xero, QuickBooks, etc.
&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoices&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="n"&gt;service_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exact-online&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;filter&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;updated_since&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-01-01T00:00:00Z&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;# Data is already normalized with line items included
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invoices&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="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;Invoice &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number&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;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&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;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&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;&lt;strong&gt;Total: 15 lines of code.&lt;/strong&gt; Authentication, rate limiting, pagination, and data normalization handled automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Native Integration&lt;/th&gt;
&lt;th&gt;Apideck Unified API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Build OAuth flow, handle refresh, manage per-region&lt;/td&gt;
&lt;td&gt;Handled via Vault UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate Limiting&lt;/td&gt;
&lt;td&gt;Implement dual-header logic&lt;/td&gt;
&lt;td&gt;Normalized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Normalization&lt;/td&gt;
&lt;td&gt;Map each field manually&lt;/td&gt;
&lt;td&gt;Pre-normalized to unified schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Related Entities&lt;/td&gt;
&lt;td&gt;Separate API calls (no $expand)&lt;/td&gt;
&lt;td&gt;Included automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;Implement cursor/offset logic&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Handling&lt;/td&gt;
&lt;td&gt;Parse OData errors&lt;/td&gt;
&lt;td&gt;Standardized error format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-region&lt;/td&gt;
&lt;td&gt;Route to correct endpoint&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Division Selection&lt;/td&gt;
&lt;td&gt;Build selection UI&lt;/td&gt;
&lt;td&gt;Handled in connection flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add QuickBooks&lt;/td&gt;
&lt;td&gt;Build second integration&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;service_id&lt;/code&gt; parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Monitor Exact API changes&lt;/td&gt;
&lt;td&gt;We handle updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Supported Exact Online Resources
&lt;/h3&gt;

&lt;p&gt;Apideck's unified Accounting API supports these Exact Online resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoices&lt;/strong&gt; (Sales Invoices) - Full CRUD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bills&lt;/strong&gt; (Purchase Invoices) - Read, Create&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt; - Read&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customers&lt;/strong&gt; - Read with filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suppliers&lt;/strong&gt; - Read with filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ledger Accounts&lt;/strong&gt; - Read&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journal Entries&lt;/strong&gt; - Read, Create&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tax Rates&lt;/strong&gt; (VAT Codes) - Read&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoice Items&lt;/strong&gt; - Read&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit Notes&lt;/strong&gt; - Read&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use Native vs. Unified APIs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Native Exact Online API When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You only need Exact Online (no other accounting platforms)&lt;/li&gt;
&lt;li&gt;You need Exact-specific features not in unified models&lt;/li&gt;
&lt;li&gt;You're building for a single tenant/region&lt;/li&gt;
&lt;li&gt;You have dedicated engineering resources for maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Unified API When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to support multiple accounting platforms&lt;/li&gt;
&lt;li&gt;You want faster time-to-market (days vs. weeks)&lt;/li&gt;
&lt;li&gt;You prefer normalized data models&lt;/li&gt;
&lt;li&gt;You want to avoid authentication complexity&lt;/li&gt;
&lt;li&gt;You don't want to maintain integrations long-term&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Option 1: Native Integration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Register at &lt;a href="https://apps.exactonline.com" rel="noopener noreferrer"&gt;Exact Online App Center&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create your OAuth application&lt;/li&gt;
&lt;li&gt;Implement the authentication flow above&lt;/li&gt;
&lt;li&gt;Build API client with rate limiting&lt;/li&gt;
&lt;li&gt;Map responses to your data model&lt;/li&gt;
&lt;li&gt;Submit for Exact review (required for production)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Option 2: Apideck Unified API
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create Your Exact Online OAuth App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even with Apideck, you need OAuth credentials. Here's the streamlined process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://apps.exactonline.com" rel="noopener noreferrer"&gt;apps.exactonline.com&lt;/a&gt; and sign in&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Register a test app"&lt;/strong&gt; (can be promoted to production later)&lt;/li&gt;
&lt;li&gt;Configure your app:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Name&lt;/strong&gt;: Your application name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URI&lt;/strong&gt;: &lt;code&gt;https://unify.apideck.com/vault/callback&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save your &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client Secret&lt;/strong&gt; immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: The Client Secret is only shown once when you create the app. Copy it before closing the dialog.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For detailed instructions with screenshots, see our &lt;a href="https://developers.apideck.com/connectors/exact-online/docs/application_owner+oauth_credentials" rel="noopener noreferrer"&gt;Exact Online OAuth Setup Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Configure Apideck&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://www.apideck.com" rel="noopener noreferrer"&gt;apideck.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to the Exact Online connector settings&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;"Use your Exact Online client credentials"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enter your Client ID and Client Secret&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Save settings"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Connect Your Users&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Apideck Vault to let your users authenticate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Test Vault in your dashboard&lt;/li&gt;
&lt;li&gt;Select your API domain (nl, be, uk, etc.)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Authorize"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your users sign in at Exact Online&lt;/li&gt;
&lt;li&gt;Connection status shows &lt;strong&gt;"Connected"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Start Making API Calls&lt;/strong&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;# Test with curl&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s2"&gt;"https://unify.apideck.com/accounting/invoices"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-apideck-app-id: YOUR_APP_ID"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-apideck-consumer-id: user-123"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-apideck-service-id: exact-online"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use our SDKs:&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;apideck&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIDECK_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;app_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIDECK_APP_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;consumer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user-123&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# List all invoices
&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoices&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="n"&gt;service_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exact-online&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create an invoice
&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoices&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="n"&gt;service_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exact-online&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&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;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;service&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="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;customer-guid&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;line_items&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;description&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;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unit_price&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.00&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_rate&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;vat-standard&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="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;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Exact Online API is powerful but complex. Between OAuth flows, multi-division architecture, regional endpoints, rate limiting, the lack of &lt;code&gt;$expand&lt;/code&gt; support, and the app approval process, building a production-ready integration takes significant effort.&lt;/p&gt;

&lt;p&gt;For teams focused on delivering value rather than maintaining integrations, unified APIs offer a compelling alternative—same data, fraction of the code, and the flexibility to add other accounting platforms without rebuilding from scratch.&lt;/p&gt;

&lt;p&gt;Whether you choose native or unified, the key is understanding Exact Online's unique requirements upfront. The division system, rate limits, regional endpoints, and OData limitations aren't optional—they're fundamental to how the API works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.exactonline.com/community/s/knowledge-base#All-All-DNO-Content-restrefdocs" rel="noopener noreferrer"&gt;Exact Online REST API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.exactonline.com" rel="noopener noreferrer"&gt;Exact Online App Center&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.exact.com/us/developers" rel="noopener noreferrer"&gt;Exact Developer Portal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/integrations/exact-online" rel="noopener noreferrer"&gt;Exact Online Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.apideck.com/connectors/exact-online/docs/application_owner+oauth_credentials" rel="noopener noreferrer"&gt;Exact Online OAuth Setup Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/integrations/exact-online/sdk/python" rel="noopener noreferrer"&gt;Python Exact Online SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/integrations/exact-online/php/python" rel="noopener noreferrer"&gt;PHP Exact Online SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>accounting</category>
      <category>unifiedapi</category>
      <category>exactonline</category>
    </item>
    <item>
      <title>NetSuite Integration Guide</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Sat, 09 May 2026 20:51:43 +0000</pubDate>
      <link>https://forem.com/apideck/netsuite-integration-guide-nap</link>
      <guid>https://forem.com/apideck/netsuite-integration-guide-nap</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;1. What NetSuite Integration Actually Means&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite integration&lt;/a&gt; establishes bidirectional data flow between NetSuite and external systems: orders, customers, inventory, transactions, invoices, and financial data. The objective is to eliminate manual entry, reduce errors, and establish a single source of truth for financial and operational data.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/3NS742TCWFuJusdXcniACr/a00e3d3147d39812146301f4b0b94e91/article__3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3NS742TCWFuJusdXcniACr/a00e3d3147d39812146301f4b0b94e91/article__3.png" alt="What NetSuite Integration Actually Means"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synchronous integrations&lt;/strong&gt; process data in near real time. The calling system waits for NetSuite's response before continuing. Use this pattern for low-volume, high-priority transactions requiring immediate confirmation: inventory availability checks, credit limit validation, and payment authorization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous integrations&lt;/strong&gt; use batch processing. Records accumulate in a queue and sync at scheduled intervals or when thresholds are met. This pattern handles high volume better and provides resilience against temporary API failures, but introduces latency between systems. Most production integrations use asynchronous patterns for the bulk of their traffic.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/6RsXzKt6oSIdgkP2gwpTJU/fbf33f861952e7dccf651eea749e5ab6/article__2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/6RsXzKt6oSIdgkP2gwpTJU/fbf33f861952e7dccf651eea749e5ab6/article__2.png" alt="What NetSuite Integration Actually Means "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common integration scenarios include &lt;a href="https://www.apideck.com/crm-api" rel="noopener noreferrer"&gt;CRM&lt;/a&gt;-to-finance flows in which customer records and sales orders move from &lt;a href="https://www.apideck.com/connectors/salesforce" rel="noopener noreferrer"&gt;Salesforce&lt;/a&gt; or &lt;a href="https://www.apideck.com/connectors/hubspot" rel="noopener noreferrer"&gt;HubSpot&lt;/a&gt; into NetSuite for invoicing and revenue recognition. The &lt;a href="https://www.apideck.com/ecommerce-api" rel="noopener noreferrer"&gt;e-commerce&lt;/a&gt;-to-ERP sync keeps product catalogs, orders, and inventory aligned between &lt;a href="https://www.apideck.com/connectors/shopify" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;, &lt;a href="https://www.apideck.com/connectors/magento" rel="noopener noreferrer"&gt;Magento&lt;/a&gt;, or custom storefronts and NetSuite. Billing system integrations push invoices, payments, and subscription events from Stripe, Chargebee, or Zuora into NetSuite for revenue management. Data warehouse pipelines extract financial and operational data from NetSuite and load it into Snowflake, BigQuery, or Redshift for analytics and reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Why Teams Build NetSuite Integrations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Manual data entry between systems introduces errors at every touchpoint. A mistyped SKU, an incorrectly applied discount, or a transposed digit in a quantity field cascades into fulfillment problems, billing disputes, and accounting reconciliation nightmares. Integration removes these human error vectors entirely.&lt;/p&gt;

&lt;p&gt;Integration enforces data ownership rules and synchronization patterns that establish which system is the single source of truth for each data domain. When customer records exist in both CRM and NetSuite with variations, integration defines which is authoritative.&lt;/p&gt;

&lt;p&gt;A well-designed integration enables business logic execution at scale: automatic credit checks before order approval, dynamic pricing based on customer tier and volume, inventory allocation across multiple warehouses, and &lt;a href="https://www.apideck.com/use-cases/tax-automation" rel="noopener noreferrer"&gt;tax calculation&lt;/a&gt; for multi-jurisdiction transactions. These workflows become possible when systems communicate reliably.&lt;/p&gt;

&lt;p&gt;For CTOs and engineering leaders, a clear integration architecture prevents the accumulation of tribal knowledge and reduces key person dependencies. It provides leverage for future system migrations since well-defined integration contracts are easier to redirect to new endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. NetSuite Integration Mechanisms&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Tradeoffs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST Web Services&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;New projects, standard CRUD, microservices&lt;/td&gt;
&lt;td&gt;Industry standard, wide record coverage. Requires OAuth setup.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SOAP (SuiteTalk)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Legacy stacks, typed schemas&lt;/td&gt;
&lt;td&gt;Mature but heavy XML overhead, being phased out.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RESTlets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom logic, performance optimization, batch operations&lt;/td&gt;
&lt;td&gt;Maximum flexibility. Requires SuiteScript expertise, consumes governance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;iPaaS/Middleware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standard patterns, rapid deployment&lt;/td&gt;
&lt;td&gt;Fast delivery. Less control, ongoing platform costs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Architecture Decision: REST API vs RESTlets&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;strong&gt;Standard REST API&lt;/strong&gt; when: you need standard CRUD operations, your business logic runs externally, you want Oracle's maintained endpoints, or you prioritize standardization and ease of onboarding new developers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;RESTlets&lt;/strong&gt; when: you need to combine multiple operations in one call to reduce round-trips, business logic must execute within NetSuite's transaction context for atomicity, you need custom response formats, or you're optimizing for governance unit consumption. A RESTlet can validate 50 line items server-side in one call instead of making 50 external validation requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oracle is actively pushing REST forward and has stopped adding new SOAP endpoints. REST should be the default for new projects unless you have specific legacy constraints requiring SOAP.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Architecture and Design Principles&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Source of Truth Decisions:&lt;/strong&gt; Define which system owns each data domain. Customer master data might live in your CRM, with NetSuite as the consumer. Product information might originate in your PIM, sync to NetSuite for pricing and inventory. Financial transactions and journal entries are almost always owned by NetSuite. Document these decisions explicitly. When conflicts arise, the source of truth system wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Modeling and Transformation:&lt;/strong&gt; NetSuite's record model rarely maps one-to-one with source systems. A Salesforce opportunity might become a NetSuite estimate, then a salesOrder, then an invoice. A single ecommerce order might create multiple NetSuite transactions if it spans subsidiaries. Build a canonical data model between systems to isolate each connector from changes in the others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync Patterns:&lt;/strong&gt; Use synchronous patterns when you need immediate confirmation: inventory checks before order confirmation, credit validation before quote acceptance. Use asynchronous patterns for everything else, especially batch jobs processing hundreds of records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Warning: Multi-Subsidiary Complexity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many NetSuite accounts serve multiple subsidiaries, currencies, and legal entities. Orders must be created in the correct subsidiary based on the customer location or ship from a warehouse. Intercompany transactions require automatic journal entries. Currency conversion rules vary by transaction type. Map these scenarios during design, not testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/6KwCsmDcYRV0UgFf5yDm9Q/657b012c09096b485ec89701f0909164/article_.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/6KwCsmDcYRV0UgFf5yDm9Q/657b012c09096b485ec89701f0909164/article_.png" alt="Architecture and Design Principles netsuite"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. Deep Dive into NetSuite APIs&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;REST Web Services&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;NetSuite's REST API follows standard JSON conventions. Standard records include customer, vendor, salesOrder, invoice, purchaseOrder, inventoryItem, and hundreds of others. Custom records defined in NetSuite are also accessible via the REST API. Lists provide reference data like terms, currencies, and custom lists.&lt;/p&gt;

&lt;p&gt;CRUD operations: POST creates records, GET retrieves by internal or external ID, PATCH performs partial updates, PUT performs full replacement or upsert, DELETE removes records. All operations return standard HTTP status codes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip: The Upsert Pattern&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;PUT&lt;/code&gt; with an external ID to perform upserts in a single call. If the record exists, it updates. If not, it creates. This eliminates the lookup call before every write operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
PUT /services/rest/record/v1/customer/eid:SF-12345

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;eid:SF-12345&lt;/code&gt; references the external ID. This reduces API calls by 50% compared to the GET then POST/PATCH pattern and simplifies error handling.&lt;/p&gt;

&lt;p&gt;The REST API supports query parameters for filtering record lists by field values, date ranges, and formula conditions. For complex queries, saved searches defined in NetSuite can be executed via API and return results as JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Search Limitations and Pagination&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Architecture Warning: 1,000 Row Limit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard REST queries return a maximum of 1,000 rows per request. For larger datasets, implement pagination using &lt;code&gt;offset&lt;/code&gt; or &lt;code&gt;pageIndex&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;GET /services/rest/record/v1/customer?limit&lt;span class="o"&gt;=&lt;/span&gt;1000&amp;amp;offset&lt;span class="o"&gt;=&lt;/span&gt;1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bulk extraction exceeding 100K records, saved searches via SuiteQL are more efficient, supporting up to 5,000 rows per page. For very large datasets, consider scheduled MapReduce scripts that export to SFTP.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Code Examples&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Create salesOrder with idempotency and proper headers:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST /services/rest/record/v1/salesOrder HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Authorization: Bearer &amp;lt;access_token&amp;gt;
Content-Type: application/json
Prefer: respond-async
NetSuite-Idempotency-Key: ord-2024-001-abc123

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"externalId"&lt;/span&gt;: &lt;span class="s2"&gt;"SHOP-ORD-98765"&lt;/span&gt;,
  &lt;span class="s2"&gt;"entity"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"item"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"items"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"item"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"789"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="s2"&gt;"quantity"&lt;/span&gt;: 10, &lt;span class="s2"&gt;"rate"&lt;/span&gt;: 25.00&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;NetSuite-Idempotency-Key&lt;/code&gt; header prevents duplicate records from being created during retries after network failures. Always include &lt;code&gt;externalId&lt;/code&gt; for future upsert operations and cross-system traceability.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;RESTlets and SuiteScript&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;RESTlets are custom endpoints built inside NetSuite using SuiteScript. They encapsulate complex business logic: accept a simplified payload, validate it, apply custom pricing rules, create the sales order, trigger related workflows, and return a response tailored to your needs.&lt;/p&gt;

&lt;p&gt;Use RESTlets when standard API operations do not meet requirements, when you want to reduce round-trips by combining multiple operations into a single call, or when business logic must execute within NetSuite's transaction context for atomicity. RESTlet execution consumes governance units, but a single RESTlet processing 100 records uses fewer units than 100 individual REST API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Concurrency, Scaling, and Costs&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Concurrency Limits and Licensing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;NetSuite limits concurrent requests per integration. Exceeding limits returns 429 Too Many Requests errors and can temporarily block your integration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;License Type&lt;/th&gt;
&lt;th&gt;Concurrent Request Limit&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;5 concurrent requests&lt;/td&gt;
&lt;td&gt;Included with NetSuite license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuiteCloud Plus&lt;/td&gt;
&lt;td&gt;10+ concurrent requests&lt;/td&gt;
&lt;td&gt;Additional licensing cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Cost Factor: SuiteCloud Plus Licensing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;High-throughput integrations processing more than 10K transactions daily typically require SuiteCloud Plus licensing for increased concurrency. Budget for this during project planning. SOAP (SuiteTalk) calls within standard limits are included in base licensing. REST calls count against governance limits regardless of license tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Worker Pool Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To maximize throughput without triggering 429 errors, implement a worker pool pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set worker count to license concurrency limit minus one (buffer for manual operations)&lt;/li&gt;
&lt;li&gt;Use a message queue (SQS, RabbitMQ, Kafka) to decouple event production from API consumption&lt;/li&gt;
&lt;li&gt;Implement exponential backoff with jitter on 429 responses&lt;/li&gt;
&lt;li&gt;Monitor queue depth to detect backpressure early
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Max Workers &lt;span class="o"&gt;=&lt;/span&gt; License Concurrency Limit - 1
Backoff &lt;span class="o"&gt;=&lt;/span&gt; min&lt;span class="o"&gt;(&lt;/span&gt;base_delay &lt;span class="k"&gt;*&lt;/span&gt; 2^attempt + random_jitter, max_delay&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture provides natural rate limiting while maximizing throughput within your license constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;7. Authentication and Security&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OAuth 2.0&lt;/strong&gt; is the required authentication mechanism for NetSuite REST APIs. Create an integration record in NetSuite, generate client credentials, and exchange them for access tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Least Privilege Roles:&lt;/strong&gt; Create a dedicated integration role with minimum required permissions. For salesOrder creation: create permission on salesOrder, read permission on customer and item records. Never use Full Access or administrator accounts for API operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token Management:&lt;/strong&gt; Store OAuth tokens in a secrets management system like HashiCorp Vault or AWS Secrets Manager. Never commit tokens to source control or log them in application logs. Automate token refresh before expiration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment Separation:&lt;/strong&gt; Maintain separate credentials for sandbox and production environments. Configure deployment pipelines to inject the correct credentials based on the target environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pre API Configuration Checklist&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enable REST Web Services: Setup &amp;gt; Company &amp;gt; Enable Features &amp;gt; SuiteCloud&lt;/li&gt;
&lt;li&gt;Create integration record: Setup &amp;gt; Integration &amp;gt; Manage Integrations&lt;/li&gt;
&lt;li&gt;Create a role with the required permissions&lt;/li&gt;
&lt;li&gt;Generate OAuth credentials and store them in Secrets Manager&lt;/li&gt;
&lt;li&gt;Test authentication in the sandbox before writing application code&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;8. Record Mapping and Data Contracts&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source Entity&lt;/th&gt;
&lt;th&gt;NetSuite Record&lt;/th&gt;
&lt;th&gt;Sublist Details&lt;/th&gt;
&lt;th&gt;Key Linkages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CRM Contact/Account&lt;/td&gt;
&lt;td&gt;Customer&lt;/td&gt;
&lt;td&gt;Address Book (addresses), Contact (contacts)&lt;/td&gt;
&lt;td&gt;External ID links to CRM ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product/SKU&lt;/td&gt;
&lt;td&gt;Inventory Item, Service Item, Non-Inventory Item&lt;/td&gt;
&lt;td&gt;Pricing Sublist, Bin Numbers&lt;/td&gt;
&lt;td&gt;Matrix items for variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commerce Order&lt;/td&gt;
&lt;td&gt;SalesOrder&lt;/td&gt;
&lt;td&gt;Item Sublist (line items)&lt;/td&gt;
&lt;td&gt;Entity field links to Customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order Line&lt;/td&gt;
&lt;td&gt;Item Sublist Row&lt;/td&gt;
&lt;td&gt;Inventory Detail (serial/lot)&lt;/td&gt;
&lt;td&gt;Item field links to the Item record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invoice&lt;/td&gt;
&lt;td&gt;Invoice&lt;/td&gt;
&lt;td&gt;Item Sublist, Apply Sublist&lt;/td&gt;
&lt;td&gt;Created From links to SalesOrder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment&lt;/td&gt;
&lt;td&gt;Customer Payment&lt;/td&gt;
&lt;td&gt;Apply Sublist&lt;/td&gt;
&lt;td&gt;Links to open Invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Custom Fields and Custom Records:&lt;/strong&gt; Most NetSuite implementations include custom fields for business-specific data. Internal IDs for custom fields differ between sandbox and production environments. Externalize these mappings in configuration rather than hardcoding them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential Integrity:&lt;/strong&gt; NetSuite enforces referential integrity. You cannot create a salesOrder for a customer that does not exist. Plan sync order carefully: customers before orders, items before order lines, parent records before child records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External IDs:&lt;/strong&gt; NetSuite supports external IDs on most record types. Store source system identifiers as external IDs. This enables the upsert pattern and prevents duplicate records when the same source record syncs multiple times.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;9. Real World Friction Points&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Sandbox Refresh Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Architecture Warning: Sandbox Refresh Breaks Integrations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When NetSuite refreshes a sandbox from production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth tokens are invalidated and require re-authentication&lt;/li&gt;
&lt;li&gt;Integration records may be overwritten with production values&lt;/li&gt;
&lt;li&gt;Internal IDs change if the sandbox has different test data&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Custom field internal IDs may shift&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Use external IDs exclusively instead of internal IDs. Store integration credentials in an external secrets manager, not in code. Build a sandbox reset runbook that re-authenticates, validates field mappings, and runs smoke tests before development resumes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Search and Data Extraction Limits&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Standard REST queries enforce a 1,000 row limit per request. Implement offset pagination for larger result sets.&lt;/p&gt;

&lt;p&gt;Saved searches via SuiteQL are more efficient for bulk extraction, supporting up to 5,000 rows per page with proper pagination tokens.&lt;/p&gt;

&lt;p&gt;For datasets exceeding 100K records, standard API pagination becomes inefficient. Consider scheduled MapReduce scripts that export data to SFTP, then pull from the export files. This approach handles millions of records without hitting API limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;10. High Volume Performance Strategies&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Beyond concurrency management, high-volume integrations require additional optimization strategies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batching:&lt;/strong&gt; Instead of creating records one at a time, batch multiple records into a single API call where supported. The REST API supports creating multiple records in one request for certain record types. Batching reduces API call counts and significantly improves throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queue Systems:&lt;/strong&gt; Implement a message queue between your source system and the NetSuite integration layer. Events enter the queue and are processed at controlled rates. This decouples event production from API consumption and provides natural backpressure when NetSuite is slow or rate-limited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry with Exponential Backoff:&lt;/strong&gt; Implement exponential backoff with jitter for transient errors. If a request fails with a 429 or 5xx error, wait progressively longer before retrying. Include random jitter to prevent thundering herd problems when multiple retry timers fire simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip: Governance Unit Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RESTlets consume governance units per execution, not per record processed. A RESTlet that processes 100 records in one call uses fewer total units than 100 individual REST API calls. For high-volume write operations, consider a RESTlet that accepts batched payloads and processes them within a single execution context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saved Searches for Bulk Retrieval:&lt;/strong&gt; When extracting data from NetSuite, saved searches are more efficient than individual record GETs. Create a saved search that returns all records modified since the last sync, selecting only the fields your integration needs. Execute via API and paginate through results.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;11. Common Pitfalls&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hardcoding Internal IDs:&lt;/strong&gt; Internal IDs differ between sandbox and production. Use external IDs or configuration files to map identifiers across environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed Business Logic:&lt;/strong&gt; When logic spreads across NetSuite scripts, middleware, and external code, no one owns the complete picture. A change in one place breaks another. Centralize business logic in one layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring Governance Limits:&lt;/strong&gt; Development never stresses limits. Load test against realistic volumes before production deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insufficient Testing:&lt;/strong&gt; Test validation errors, network failures, rate limit responses, partial batch successes, and concurrent requests. Production will encounter all of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing Error Recovery:&lt;/strong&gt; Build automatic retry with exponential backoff. Build dead letter queues for records that fail repeatedly. Build tooling to review and reprocess failed records.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;12. Alternatives to Building In-House&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Integration Multiplication Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;NetSuite is rarely the only system requiring integration. Most B2B SaaS products eventually need multiple accounting systems: NetSuite for enterprise customers, &lt;a href="https://www.apideck.com/connectors/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt; for small businesses, &lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt; for international markets, and &lt;a href="https://www.apideck.com/connectors/sage-intacct" rel="noopener noreferrer"&gt;Sage&lt;/a&gt; for specific verticals. Each system has its own API, authentication mechanism, data model, and operational quirks. Building and maintaining separate integrations multiplies engineering investment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Unified API Approach&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/what-is-a-unified-api" rel="noopener noreferrer"&gt;Unified APIs&lt;/a&gt; provide a single integration surface connecting to multiple downstream systems. Instead of building separate NetSuite, QuickBooks, and Xero integrations, you build one integration to the unified API. The unified API handles mapping your canonical data model to each accounting system's format.&lt;/p&gt;

&lt;p&gt;Platforms like Apideck offer pre-built connectors across &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting&lt;/a&gt;, &lt;a href="https://www.apideck.com/crm-api" rel="noopener noreferrer"&gt;CRM&lt;/a&gt;, &lt;a href="https://www.apideck.com/hris-api" rel="noopener noreferrer"&gt;HRIS&lt;/a&gt;, and other categories with standardized data models and authentication flows. This approach abstracts the complexity of maintaining individual API connections.&lt;/p&gt;

&lt;p&gt;Advantages: faster time to value (days instead of months for new systems), reduced maintenance burden (provider handles API changes and version updates), consistent data models across all connected systems, shared authentication patterns, and engineering time focused on core product differentiation rather than integration plumbing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;When Each Approach Fits&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Consider a unified API&lt;/strong&gt; when your product needs to support multiple accounting or ERP systems to serve different customer segments, when you want to accelerate time-to-market for new integrations, when your engineering team lacks deep expertise in each target API, or when you want to reduce ongoing maintenance costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build direct&lt;/strong&gt; when you need extremely deep NetSuite functionality that unified APIs do not expose, when your integration requirements are highly specialized, or when you have an existing team with NetSuite expertise and available capacity.&lt;/p&gt;

&lt;p&gt;The choice is ultimately an engineering resource allocation decision. Both approaches work. The right answer depends on your constraints, timeline, and long-term product strategy.&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>saas</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Accounting Integration</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Tue, 28 Apr 2026 17:05:47 +0000</pubDate>
      <link>https://forem.com/apideck/accounting-integration-3hpk</link>
      <guid>https://forem.com/apideck/accounting-integration-3hpk</guid>
      <description>&lt;p&gt;Your finance team didn't sign up to be data-entry clerks. Neither did your customers.&lt;/p&gt;

&lt;p&gt;Yet every day, thousands of businesses manually export transactions from QuickBooks, copy invoice data from Xero, or reconcile payments between three different spreadsheets. It's slow, error-prone, and a waste of skilled people's time.&lt;/p&gt;

&lt;p&gt;Accounting integrations fix this. They connect your software directly to your customers' general ledger, automatically syncing invoices, expenses, payments, and journal entries. No CSV exports. No copy-paste errors. No "we'll reconcile it at month-end."&lt;/p&gt;

&lt;p&gt;This guide covers what accounting integrations actually do, the most common use cases, and how companies like Ramp, Airbase, and BILL have leveraged them to gain a competitive advantage.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;open accounting movement&lt;/a&gt; is driving demand for standardized data access across accounting platforms, making integration capabilities a competitive requirement for B2B SaaS products.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an Accounting Integration?
&lt;/h2&gt;

&lt;p&gt;An accounting integration is a connection between your application and an accounting platform like &lt;a href="https://www.apideck.com/connectors/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/connectors/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, or &lt;a href="https://www.apideck.com/connectors/sage-intacct" rel="noopener noreferrer"&gt;Sage Intacct&lt;/a&gt;. It allows data to flow between systems, either one-way or bidirectionally, without manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick definition, if you're new to this:&lt;/strong&gt; A general ledger (GL) is the master record of all financial transactions in a business. Every transaction gets tagged with a GL code, which is essentially a label that categorizes where the money went: "Travel," "Software Subscriptions," "Office Supplies," and so on. When we talk about syncing to the GL, we mean getting your data into that master record with the right labels attached.&lt;/p&gt;

&lt;p&gt;At a practical level, accounting integrations mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoices&lt;/strong&gt; created in your billing system appear automatically in your customer's accounting software
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expenses&lt;/strong&gt; logged in your app push directly to the general ledger with the correct GL codes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt; recorded in one system update the other in real time
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bank transactions&lt;/strong&gt; reconcile automatically instead of requiring manual matching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternative is what most businesses still do: export a CSV, open Excel, clean the data, reformat it for the accounting system, import it, and hope nothing gets lost in translation. Multiply that by hundreds of transactions per month, and you've created busywork that produces no value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Accounting Integrations Matter for Your Product
&lt;/h2&gt;

&lt;p&gt;If you're building software that touches money (invoicing, expense management, payroll, procurement, lending, payments), your customers will eventually ask: "Does this connect to our accounting system?"&lt;/p&gt;

&lt;p&gt;The answer determines whether you're a tool they'll actually adopt or one they'll abandon after the free trial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business case is straightforward:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Faster sales cycles.&lt;/strong&gt; Enterprise buyers have procurement checklists. "Integrates with NetSuite" is often a required line item. Without it, you don't make the shortlist.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower churn.&lt;/strong&gt; Customers who integrate your product into their financial workflows don't leave easily. The switching cost is too high when your app is integrated into their month-end close process.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher contract values.&lt;/strong&gt; Integration features justify premium pricing. Ramp charges more for deeper ERP connections. So does BILL. Customers pay for the time savings.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced support burden.&lt;/strong&gt; Every "how do I export this to QuickBooks?" ticket is a symptom of a missing integration. Build it once, eliminate the ticket category.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market expansion.&lt;/strong&gt; Companies across specific regions, industries, and sizes use certain accounting tools. SMBs gravitate toward QuickBooks and Xero. Mid-market and enterprise prefer NetSuite, Sage Intacct, and Microsoft Dynamics. Offering integrations with the tools your target markets rely on opens doors that would otherwise stay closed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two Types of Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;Before diving into use cases, it's worth distinguishing between two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Customer-Facing (Product) Integrations&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These connect your product to your customers' accounting systems. They're what this guide focuses on—the integrations that become product features, drive sales conversations, and require supporting hundreds of different customer configurations.&lt;/p&gt;

&lt;p&gt;The rest of this guide addresses customer-facing integrations, which present unique challenges around multi-tenancy, authorization, and scale.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Internal Integrations&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These connect your company's own systems. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRM → Accounting:&lt;/strong&gt; When an opportunity closes in Salesforce, automatically create the customer in NetSuite for invoicing
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting → BI:&lt;/strong&gt; Sync financial data from Xero to Tableau for executive dashboards
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting → Slack:&lt;/strong&gt; Alert the finance channel when invoices are overdue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are typically built with &lt;a href="https://www.apideck.com/blog/top-embedded-ipaas-solutions" rel="noopener noreferrer"&gt;iPaaS tools&lt;/a&gt; (Workato, Tray) or custom scripts. They're one-off connections serving your internal workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 Common Use Cases for Accounting Integrations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Expense Management → General Ledger Sync
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Employees submit expenses. Finance approves them. Then someone manually enters each line item into QuickBooks with the correct GL code, department, and tax treatment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Approved expenses push automatically to the accounting system. GL codes mapped by expense category. The finance team reviews exceptions, not every transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Brex, Expend, ExpenseOnDemand, Ramp, and Airbase all sync expenses directly to QuickBooks, Xero, NetSuite, and Sage Intacct. For corporate card products, this is now table stakes. &lt;a href="https://www.apideck.com/customer-cases/invoice2go-bill-com-integrations" rel="noopener noreferrer"&gt;Invoice2go by BILL&lt;/a&gt; used Apideck's Unified API to speed up accounting integrations across multiple platforms, enabling support for major systems without custom builds. This helped them scale expense and reporting features quickly across over 220,000 customers worldwide.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Below is what the QuickBooks Online integration looks like on Ramp after you set it up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/5Cywz71LzRmqG3TeHJVfLF/184288b5b8c5ec4f6876f134236be411/Screenshot_2024-04-11_at_5.37.51%C3%A2__PM.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/5Cywz71LzRmqG3TeHJVfLF/184288b5b8c5ec4f6876f134236be411/Screenshot_2024-04-11_at_5.37.51%C3%A2__PM.png" alt="Below is what the QuickBooks Online integration looks like on Ramp after you set it up. "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Invoicing and Billing → Accounts Receivable
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your product generates invoices. Your customer's accounting team needs those invoices in their accounts receivable ledger. Right now, they're downloading PDFs and manually creating entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Invoices created in your system automatically appear in the customer's accounting platform. Payment status syncs back. When the invoice is paid, both systems reflect it without anyone having to touch a keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Stripe's invoicing connects to QuickBooks and Xero. Chargebee syncs subscription invoices to major accounting platforms. Any serious billing tool offers this now. For example, &lt;a href="https://www.apideck.com/customer-cases/roopairs-quickbooks-online-integration" rel="noopener noreferrer"&gt;Roopairs&lt;/a&gt; used Apideck's Unified API to launch a QuickBooks Online integration quickly, saving roughly 40 developer hours and supporting 30 active customers from day one. The team is now planning expansions to Sage Intacct and NetSuite due to the speed and simplicity of the implementation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stripe QuickBooks Online integration guide&lt;/em&gt;&lt;br&gt;
&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/N50tPlVmWMqLOm6agKAua/f3bb43b5ea06b5b897dd20ec2b579f51/Screenshot_2026-01-13_at_23.51.20_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/N50tPlVmWMqLOm6agKAua/f3bb43b5ea06b5b897dd20ec2b579f51/Screenshot_2026-01-13_at_23.51.20_2x.png" alt="Screenshot 2026-01-13 at 23.51.20@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Procurement and AP Automation → Purchase Order Matching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; A company issues a purchase order. Goods arrive. An invoice comes in. Someone has to manually match the PO, receipt, and invoice (the "three-way match") before approving payment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Your procurement system shares PO data with the accounting platform. When invoices arrive, matching happens automatically. Exceptions flag for review; clean matches process straight through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Coupa, SAP Ariba, and newer players like Zip connect procurement workflows directly to ERPs for automated matching and approval routing. For example, &lt;a href="https://www.apideck.com/customer-cases/derive-erp-accounting-integration" rel="noopener noreferrer"&gt;Derive&lt;/a&gt; used Apideck's Unified API to reduce development time by about 70 percent. They went from sign-up to a live Xero integration in just three weeks and later launched a Workday connection in under 90 days, which helped accelerate sales cycles and expand into enterprise markets more quickly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Coupa Invoice Matching Explained&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/3RBqgwzhrjAA2R5m1RPEnN/50b104fbc6e96cf8389c7b96f118fba2/Screenshot_2026-01-13_at_23.58.44_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3RBqgwzhrjAA2R5m1RPEnN/50b104fbc6e96cf8389c7b96f118fba2/Screenshot_2026-01-13_at_23.58.44_2x.png" alt="Coupa Integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Revenue Recognition → Compliance with Accounting Standards
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; SaaS companies with usage-based pricing or multi-element contracts can't just recognize revenue when cash hits the bank. Accounting standards like ASC 606 require recognizing revenue over the service period. In plain English: you have to follow specific rules about &lt;em&gt;when&lt;/em&gt; you're allowed to say you've "earned" money. Calculating this manually across thousands of subscriptions is an audit nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Your billing system sends contract and usage data to the accounting platform. Revenue schedules are generated automatically based on the rules. Auditors get clean documentation without your team building spreadsheets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Stripe Revenue Recognition, Chargebee's RevRec module, and dedicated tools like Leapfin pull transaction data and push compliant journal entries to NetSuite or Sage Intacct.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chargebee RevRec Features&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/1OIsgeq1MjjXtwfmzks9dx/2b7b16d7a31839a497c1289cc4d45828/Screenshot_2026-01-14_at_00.08.44_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/1OIsgeq1MjjXtwfmzks9dx/2b7b16d7a31839a497c1289cc4d45828/Screenshot_2026-01-14_at_00.08.44_2x.png" alt="Chargebee RevRec Features"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Embedded Fintech → Real-Time Cash Flow Visibility
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Lending platforms, cash flow tools, and financial dashboards need to see a business's real financial position. Bank feeds show cash movement, but not the full picture: receivables, payables, revenue trends, and outstanding invoices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Pull chart of accounts, invoices, bills, and journal entries from the customer's accounting system. Display real-time financial health. Underwrite loans based on actual accounting data, not uploaded bank statements from three months ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; &lt;a href="https://www.openbankingtracker.com/api-aggregators/plaid" rel="noopener noreferrer"&gt;Plaid&lt;/a&gt; handles banking data; accounting integrations from providers like Codat and Apideck fill in the rest. Lenders like Clearco and Pipe use accounting data to underwrite in hours instead of weeks.&lt;/p&gt;

&lt;p&gt;For a technical deep-dive on this pattern, see &lt;a href="https://www.apideck.com/blog/using-accounting-apis-for-smart-lending-decisions" rel="noopener noreferrer"&gt;Using Accounting APIs for Smart Lending Decisions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plaid API Docs&lt;/em&gt; &lt;br&gt;
&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/4N7Qk2Xy05BDl45nj88tLy/51a18107db9d04b5eae121324e8eb0ef/Screenshot_2026-01-14_at_00.19.11_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/4N7Qk2Xy05BDl45nj88tLy/51a18107db9d04b5eae121324e8eb0ef/Screenshot_2026-01-14_at_00.19.11_2x.png" alt="Plaid API Docs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. AI and Machine Learning → Financial Intelligence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You want to offer AI-powered features: budget predictions, anomaly detection, and cash flow forecasting, but your models need comprehensive financial data from each customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Pull balance sheets, income statements, transaction histories, and accounts receivable aging from customers' accounting systems. Feed this data to your ML models for personalized insights. As new transactions flow in, models update automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Runway, Jirav, and Mosaic pull accounting data to power FP&amp;amp;A automation. Fintech platforms use it to build credit models that outperform traditional underwriting.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Runway Fintech Platform&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/ri7m0E6ZiXCO8bElv0CGE/d1f57078c78bec8e91857c962c0b2739/Screenshot_2026-01-14_at_00.10.41_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/ri7m0E6ZiXCO8bElv0CGE/d1f57078c78bec8e91857c962c0b2739/Screenshot_2026-01-14_at_00.10.41_2x.png" alt="Runway"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Companies Build Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;This is where product decisions become engineering reality. You have three paths:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Build Direct Integrations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/5hZHxvvvRpIJeSslAJecUa/9bda6a322d00b38b3cbfc0541fb99568/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-185243.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/5hZHxvvvRpIJeSslAJecUa/9bda6a322d00b38b3cbfc0541fb99568/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-185243.png" alt="multiple-integrations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect to each accounting platform's API individually. QuickBooks Online has a REST API. Xero uses OAuth 2.0. NetSuite offers both SOAP and REST. Sage has &lt;a href="https://www.apideck.com/blog/the-sage-api-playbook-why-sage-cloud-is-not-one-api" rel="noopener noreferrer"&gt;several APIs, depending on which Sage product you're targeting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Full control over the integration. Access to every feature the platform offers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Each integration is a significant investment. Industry estimates indicate that a single integration requires 150+ engineering hours to build and 300+ hours annually to maintain, with total costs ranging from $10,000 to $50,000 per integration per year, including engineering time, customer success support, and ongoing maintenance. Supporting 10 accounting systems means multiplying that investment tenfold. Teams routinely estimate six weeks and ship in three months, as auth flows alone can derail timelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The maintenance reality:&lt;/strong&gt; Accounting APIs change. QuickBooks pushes updates. Xero deprecates endpoints. NetSuite releases new versions. When a sync breaks on the first of the month (the worst possible timing for any finance team), someone on your team is firefighting instead of building features. Direct integrations require ongoing "on-call" capacity that most teams underestimate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Use a Unified API
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/NyNWfDtAshr8iXXuLpEWs/038bb8e0a430ca8cf75c28b20bb525e6/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-190250.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/NyNWfDtAshr8iXXuLpEWs/038bb8e0a430ca8cf75c28b20bb525e6/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-190250.png" alt="unified api managed integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A unified API provider normalizes multiple accounting platforms into a single integration. Think of it as a universal translator: your system speaks one language, and the provider handles the dialects of QuickBooks, Xero, NetSuite, and the rest.&lt;/p&gt;

&lt;p&gt;You build once; they maintain the connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Launch in weeks instead of months. One data model to learn. One auth flow to implement. Coverage across platforms without multiplying your engineering investment. Unified API approaches typically deliver 3-5x faster implementation with significantly lower total cost of ownership over multi-year periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; You're dependent on the provider's coverage depth and data model. Some edge cases or advanced features may still require direct API work for specific platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Providers in this space:&lt;/strong&gt; Apideck, Merge, Codat, Railz, and Rutter all offer unified accounting APIs with varying coverage, pricing models, and approaches to data normalization. For a detailed comparison, see &lt;a href="https://www.apideck.com/blog/top-merge-api-alternatives" rel="noopener noreferrer"&gt;Top Merge API Alternatives for SaaS Teams in 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not sure whether to build or buy? Read &lt;a href="https://www.apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;Build vs Buy Accounting Integrations&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Hybrid Approach
&lt;/h3&gt;

&lt;p&gt;Use a unified API for 80% of your coverage (common platforms and standard use cases), then build direct integrations for strategic accounts that require deep customization. This typically means NetSuite or SAP for enterprise deals that require custom fields, advanced workflows, or specific ERP modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Prioritize Which Integrations to Build
&lt;/h2&gt;

&lt;p&gt;As you can't build everything at once. Use this framework to prioritize and insert the Accounting integrations that are relevant:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;QuickBooks&lt;/th&gt;
&lt;th&gt;NetSuite&lt;/th&gt;
&lt;th&gt;Xero&lt;/th&gt;
&lt;th&gt;Sage Intacct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;% of pipeline requesting&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected close rate lift&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention impact&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New market access&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering effort&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance burden&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weighted Score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Integration Prioritization Scorecard
&lt;/h3&gt;

&lt;h3&gt;
  
  
  How to use it:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Survey your sales team: which integrations come up most in deals?
&lt;/li&gt;
&lt;li&gt;Analyze lost deals: how many cited missing integrations?
&lt;/li&gt;
&lt;li&gt;Segment by customer size: SMB skews QuickBooks/Xero, enterprise skews NetSuite/Sage
&lt;/li&gt;
&lt;li&gt;Score each integration, calculate weighted totals, and stack-rank&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents the common mistake of building integrations based on engineering interest rather than business impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls: What Goes Wrong with Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;Most integration guides tell you what to build. Few tell you what breaks. Here are the failure modes that create support tickets, churn, and 2 AM pages:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Auth Token Expiry and Refresh Failures
&lt;/h3&gt;

&lt;p&gt;OAuth tokens expire. Refresh tokens have their own lifespans. QuickBooks tokens last 100 days; if a customer doesn't use the integration for three months, it silently breaks. Your sync stops, but no one notices until month-end close—when the finance team is already stressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Proactive token health monitoring. Alert customers &lt;em&gt;before&lt;/em&gt; re-authentication is needed, not after data stops flowing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Schema Drift and API Changes
&lt;/h3&gt;

&lt;p&gt;Accounting platforms don't freeze their APIs. QuickBooks pushed breaking changes to their invoice endpoints in 2023. Xero periodically deprecates fields. NetSuite's SuiteTalk versions introduce incompatibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Version your integrations. Monitor API changelogs. Build regression tests that catch schema changes before customers do.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rate Limit Hell
&lt;/h3&gt;

&lt;p&gt;QuickBooks allows 500 requests per minute. Sounds generous until you're syncing 10,000 invoices for a new customer. Hit the limit, and your sync backs up. Customers see stale data. Support tickets pile up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Implement exponential backoff. Queue and batch requests. Design for burst limits from day one, not after you hit them in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multi-Entity and Multi-Currency Complications
&lt;/h3&gt;

&lt;p&gt;Enterprise customers don't have one QuickBooks company. They often manage twelve subsidiaries across three currencies. Your integration works perfectly for single-entity SMBs, then breaks spectacularly when an enterprise customer connects their consolidated NetSuite instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Design for multi-entity from the start. Ask during onboarding: "How many entities will you connect?" If the answer is more than one, adjust your data model accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. GL Code Mapping Mismatches
&lt;/h3&gt;

&lt;p&gt;Your expense categories don't match your customer's chart of accounts. "Travel" in your system needs to map to "6200 - Employee Travel &amp;amp; Entertainment" in theirs. Get it wrong, and transactions land in the wrong accounts or fail to sync entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Build a mapping interface. Let customers configure how your categories translate to their GL codes. Don't assume your defaults work for everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Partnership Agreement Delays
&lt;/h3&gt;

&lt;p&gt;Many enterprise accounting vendors (especially Sage, Xero, Intuit, and SAP) require formal partnership agreements before you can access sandbox environments or API documentation. These agreements can take months, sometimes over a year, and cost tens of thousands annually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Start partnership applications early, before you need the integration. If the timeline is critical, consider unified API providers who already have these partnerships in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look for in an Accounting Integration Solution
&lt;/h2&gt;

&lt;p&gt;Whether you're evaluating unified API providers or scoping direct builds, here's what actually matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Platform coverage.&lt;/strong&gt; Does it support the accounting systems your customers use? QuickBooks and Xero dominate SMB. NetSuite, Sage Intacct, and Microsoft Dynamics serve the mid-market and enterprise. Know your customer base before choosing. Need a full breakdown? See &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 2025&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data model depth.&lt;/strong&gt; Can you access invoices, payments, journal entries, tracking categories, tax rates, and custom fields? Or just the basics? Shallow integrations create support tickets later.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write access.&lt;/strong&gt; Reading data is the easy part. Pushing invoices, expenses, or journal entries back to the accounting system is harder but more valuable. Confirm bidirectional sync works if you need it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth experience.&lt;/strong&gt; Your customers connect to your accounting system via an OAuth flow. If that flow is clunky, confusing, or breaks frequently, your support team will hear about it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks and real-time sync.&lt;/strong&gt; Polling for changes is slow and burns API quota. Webhooks let you react to new invoices or payments the moment they happen.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and compliance.&lt;/strong&gt; You're touching general ledger data. Customers will ask about SOC 2 compliance, data encryption, and where their credentials are stored. Have answers ready, or choose a provider who does.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration observability.&lt;/strong&gt; When syncs fail, can you see why? Look for logging, error categorization, and alerting that help your support team diagnose issues without escalating to engineering.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Accounting integrations aren't optional anymore. If your product touches financial data, your customers expect it to connect to their accounting system.&lt;/p&gt;

&lt;p&gt;You can spend engineering years building and maintaining direct connections. Or you can use a unified API to launch in weeks and expand coverage as you grow.&lt;/p&gt;

&lt;p&gt;The days of "export to CSV" are ending. Give customers the integration, or watch them find a product that does.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How long does an accounting integration take to build in-house?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It depends on the platform and your requirements. Simple read-only integrations might take 4-6 weeks. Bidirectional sync with error handling, rate limiting, and multi-entity support typically takes 3-6 months. If the vendor requires a partnership agreement (NetSuite, SAP), add 3-12 months for paperwork alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much do accounting integrations cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Direct builds run $10,000-$50,000 per integration annually when you factor in engineering time, maintenance, and support. Unified API providers typically charge $500-$2,000/month for access to dozens of integrations. The break-even point is usually 2-3 integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the most popular accounting systems to integrate with?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Varies by segment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SMB:&lt;/strong&gt; QuickBooks Online, Xero, FreshBooks, Wave
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mid-market:&lt;/strong&gt; Sage Intacct, NetSuite, Microsoft Dynamics 365 Business Central
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; NetSuite, SAP, Oracle, Microsoft Dynamics 365 Finance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What data typically gets synced?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Common objects include: invoices, bills, payments, journal entries, chart of accounts, customers/vendors, tax rates, tracking categories, purchase orders, and bank transactions. Advanced use cases add balance sheets, income statements, and aged receivables/payables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I start with one integration and add more later?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, but plan your data model carefully. If you build for QuickBooks first with a QuickBooks-specific schema, adding Xero later means refactoring. Starting with a normalized data model (or using a unified API) avoids this trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go Deeper: Related Guides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Building your first integration?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/how-to-integrate-with-quickbooks-api" rel="noopener noreferrer"&gt;How to Integrate with the QuickBooks API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/integrating-with-the-netsuite-rest-api" rel="noopener noreferrer"&gt;A Guide to Integrating with the NetSuite REST API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/your-guide-to-building-a-sage-intacct-api-integration" rel="noopener noreferrer"&gt;Your Guide to Building a Sage Intacct API Integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/create-a-workday-rest-api-integration" rel="noopener noreferrer"&gt;How to create a Workday REST API Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Evaluating build vs. buy?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;Build vs Buy Accounting Integrations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/top-merge-api-alternatives" rel="noopener noreferrer"&gt;Top Merge API Alternatives for SaaS Teams in 2025&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&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;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Designing for scale and reliability?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/12-best-practices-for-accounting-integrations-in-vertical-saas" rel="noopener noreferrer"&gt;12 Best Practices for Accounting Integrations in Vertical SaaS&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/unified-apis-for-fintech-when-point-integrations-stop-scaling" rel="noopener noreferrer"&gt;Unified APIs for Fintech: When Point Integrations Stop Scaling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Sage ecosystem?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/untangling-the-sage-ecosystem" rel="noopener noreferrer"&gt;Untangling The Sage Ecosystem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/the-sage-api-playbook-why-sage-cloud-is-not-one-api" rel="noopener noreferrer"&gt;The Sage API Playbook: Why 'Sage Cloud' Is Not One API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/sage-integration-field-guide" rel="noopener noreferrer"&gt;The Apideck Field Guide to the Sage Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Specific use cases?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/using-accounting-apis-for-smart-lending-decisions" rel="noopener noreferrer"&gt;Using Accounting APIs for Smart Lending Decisions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;Bank Feeds API Integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/from-accounts-receivable-to-lending-automation-integration-use-cases-for-vertical-saas" rel="noopener noreferrer"&gt;From Accounts Receivable to Lending Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ready to add accounting integrations to your product?&lt;/strong&gt; &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Explore Apideck's Unified Accounting API&lt;/a&gt;: one integration, 35+ accounting platforms, live in days.&lt;/p&gt;

</description>
      <category>api</category>
      <category>accounting</category>
      <category>accountingintegration</category>
      <category>apiintegration</category>
    </item>
    <item>
      <title>Your MCP Server Is Eating Your Context Window. There's a Simpler Way</title>
      <dc:creator>samz</dc:creator>
      <pubDate>Mon, 16 Mar 2026 15:31:45 +0000</pubDate>
      <link>https://forem.com/apideck/your-mcp-server-is-eating-your-context-window-theres-a-simpler-way-315b</link>
      <guid>https://forem.com/apideck/your-mcp-server-is-eating-your-context-window-theres-a-simpler-way-315b</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MCP tool definitions can burn 55,000+ tokens before an agent processes a single user message. We built the Apideck CLI as an AI-agent interface instead:an ~80-token agent prompt replaces tens of thousands of tokens of schema, with progressive disclosure via &lt;code&gt;--help&lt;/code&gt; and structural safety baked into the binary. Any agent that can run shell commands can use it. No protocol support required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem demos never show you
&lt;/h2&gt;

&lt;p&gt;Here's a scenario that'll feel familiar if you've wired up MCP servers for anything beyond a demo.&lt;/p&gt;

&lt;p&gt;You connect GitHub, Slack, and Sentry. Three services, maybe 40 tools total. Before your agent has read a single user message, &lt;a href="https://www.mmntm.net/articles/mcp-context-tax" rel="noopener noreferrer"&gt;55,000 tokens of tool definitions&lt;/a&gt; are sitting in the context window. That's over a quarter of Claude's 200k limit. Gone.&lt;/p&gt;

&lt;p&gt;It gets worse. Each MCP tool costs &lt;a href="https://www.mmntm.net/articles/mcp-context-tax" rel="noopener noreferrer"&gt;550-1,400 tokens&lt;/a&gt; for its name, description, JSON schema, field descriptions, enums, and system instructions. Connect a real API surface, say a SaaS platform with 50+ endpoints, and you're looking at 50,000+ tokens just to describe what the agent &lt;em&gt;could&lt;/em&gt; do, with almost nothing left for what it &lt;em&gt;should&lt;/em&gt; do.&lt;/p&gt;

&lt;p&gt;One team &lt;a href="https://www.agentpmt.com/articles/thousands-of-mcp-tools-zero-context-left-the-bloat-tax-breaking-ai-agents" rel="noopener noreferrer"&gt;reported&lt;/a&gt; three MCP servers consuming 143,000 of 200,000 tokens. That's 72% of the context window burned on tool definitions. The agent had 57,000 tokens left for the actual conversation, retrieved documents, reasoning, and response. Good luck building anything useful in that space.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical concern. David Zhang (&lt;a href="https://x.com/dzhng/status/2029518820872945889" rel="noopener noreferrer"&gt;@dzhng&lt;/a&gt;), building Duet, described ripping out their MCP integrations entirely, even after getting OAuth and dynamic client registration working. The tradeoff was impossible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load everything up front&lt;/strong&gt; → lose working memory for reasoning and history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit integrations&lt;/strong&gt; → agent can only talk to a few services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build dynamic tool loading&lt;/strong&gt; → add latency and middleware complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;He called it a "trilemma."&lt;/p&gt;

&lt;p&gt;And the numbers hold up under controlled testing. A &lt;a href="https://www.scalekit.com/blog/mcp-vs-cli-use" rel="noopener noreferrer"&gt;recent benchmark by Scalekit&lt;/a&gt; ran 75 head-to-head comparisons (same model, Claude Sonnet 4, same tasks, same prompts) and found MCP costing &lt;strong&gt;4 to 32x more tokens&lt;/strong&gt; than CLI for identical operations. Their simplest task, checking a repo's language, consumed 1,365 tokens via CLI and 44,026 via MCP. The overhead is almost entirely schema: 43 tool definitions injected into every conversation, of which the agent uses one or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three approaches to the same problem
&lt;/h2&gt;

&lt;p&gt;The industry is converging on three responses to context bloat. Each has a sweet spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP with compression tricks
&lt;/h3&gt;

&lt;p&gt;The first response is to keep MCP but fight the bloat. Teams compress schemas, use tool search to load definitions on demand, or build middleware that slices OpenAPI specs into smaller chunks.&lt;/p&gt;

&lt;p&gt;This works for small, well-defined interactions like looking up an issue, creating a ticket, or fetching a document. MCP's structured tool calls and typed schemas are genuinely useful when you have a tight set of operations that agents use frequently.&lt;/p&gt;

&lt;p&gt;But it adds infrastructure. You need a tool registry, search logic, caching, and routing. You're building a service to manage your services. And you're still paying per-tool token costs every time the agent decides it needs a new capability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code execution
&lt;/h3&gt;

&lt;p&gt;The code execution approach treats the agent like a developer with a persistent workspace. When the agent needs a new integration, it reads the API docs, writes code against the SDK, runs it, and saves the script for reuse. Duet pioneered this pattern by letting agents write and maintain their own integration scripts.&lt;/p&gt;

&lt;p&gt;This is powerful for long-lived workspace agents that maintain state across sessions and need complex workflows: loops, conditionals, polling, batch operations. Things that are awkward to express as individual tool calls become natural in code.&lt;/p&gt;

&lt;p&gt;There's a more targeted variant worth watching: &lt;a href="https://blog.cloudflare.com/code-mode/" rel="noopener noreferrer"&gt;Code Mode&lt;/a&gt;. Instead of writing arbitrary code against raw APIs, the agent writes short orchestration scripts that call structured MCP tools underneath. A &lt;a href="https://www.portofcontext.com/blog/cli-vs-mcp-vs-code-mode" rel="noopener noreferrer"&gt;benchmark by Sideko&lt;/a&gt; across 12 Stripe tasks showed Code Mode MCP using 58% fewer tokens than raw MCP and 56% fewer than CLI. The key insight: on multi-step tasks like creating an invoice with line items, CLI required 19 LLM round trips, raw MCP needed 12, and Code Mode collapsed it to 4. The agent writes a TypeScript program that handles the looping internally, without going back to the LLM at each step.&lt;/p&gt;

&lt;p&gt;This matters because CLI's efficiency advantage, which is real for single-step discovery and reads, can erode on complex chained writes where each round trip compounds context. Code Mode offers a middle ground: structured tool access without the schema bloat, plus the ability to batch operations without per-step LLM overhead.&lt;/p&gt;

&lt;p&gt;The tradeoff is that your agent is writing and executing code against production APIs. Even sandboxed, the safety surface is larger than a CLI with structural permissions. You need review mechanisms and trust in your agent's judgment. But for workflows that involve loops and dependent state, it's a pattern worth considering alongside CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI as the agent interface
&lt;/h3&gt;

&lt;p&gt;The third approach is the one we took. Instead of loading schemas into the context window or letting the agent write integration code, you give it a CLI.&lt;/p&gt;

&lt;p&gt;A well-designed CLI is a progressive disclosure system by nature. When a human developer needs to use a tool they haven't touched before, they don't read the entire API reference. They run &lt;code&gt;tool --help&lt;/code&gt;, find the subcommand they need, run &lt;code&gt;tool subcommand --help&lt;/code&gt;, and get the specific flags for that operation. They pay attention costs proportional to what they actually need.&lt;/p&gt;

&lt;p&gt;Agents can do exactly the same thing. And the token economics are dramatically different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CLIs are the pragmatic sweet spot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Progressive disclosure saves tokens
&lt;/h3&gt;

&lt;p&gt;Here's what the &lt;a href="https://github.com/apideck-libraries/cli" rel="noopener noreferrer"&gt;Apideck CLI&lt;/a&gt; agent prompt looks like. This is the entire thing an AI agent needs in its system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use `apideck` to interact with the Apideck Unified API.
Available APIs: `apideck --list`
List resources: `apideck &amp;lt;api&amp;gt; --list`
Operation help: `apideck &amp;lt;api&amp;gt; &amp;lt;resource&amp;gt; &amp;lt;verb&amp;gt; --help`
APIs: accounting, ats, crm, ecommerce, hris, ...
Auth is pre-configured. GET auto-approved. POST/PUT/PATCH prompt (use --yes). DELETE blocked (use --force).
Use --service-id &amp;lt;connector&amp;gt; to target a specific integration.
For clean output: -q -o json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's ~80 tokens. Compare that to the alternatives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Tokens consumed&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full OpenAPI spec in context&lt;/td&gt;
&lt;td&gt;30,000-100,000+&lt;/td&gt;
&lt;td&gt;Before first message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP tools (~3,600 per API)&lt;/td&gt;
&lt;td&gt;10,000-50,000+&lt;/td&gt;
&lt;td&gt;Before first message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI agent prompt&lt;/td&gt;
&lt;td&gt;~80&lt;/td&gt;
&lt;td&gt;Before first message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI &lt;code&gt;--help&lt;/code&gt; call&lt;/td&gt;
&lt;td&gt;~50-200&lt;/td&gt;
&lt;td&gt;Only when needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent starts with 80 tokens of guidance and discovers capabilities on demand:&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;# Level 1: What APIs are available? (~20 tokens output)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;apideck &lt;span class="nt"&gt;--list&lt;/span&gt;
accounting ats connector crm ecommerce hris ...

&lt;span class="c"&gt;# Level 2: What can I do with accounting? (~200 tokens output)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;apideck accounting &lt;span class="nt"&gt;--list&lt;/span&gt;
Resources &lt;span class="k"&gt;in &lt;/span&gt;accounting API:

  invoices
    list       GET  /accounting/invoices
    get        GET  /accounting/invoices/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    create     POST /accounting/invoices
    delete     DELETE /accounting/invoices/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

  customers
    list       GET  /accounting/customers
    ...

&lt;span class="c"&gt;# Level 3: How do I create an invoice? (~150 tokens output)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;apideck accounting invoices create &lt;span class="nt"&gt;--help&lt;/span&gt;
Usage: apideck accounting invoices create &lt;span class="o"&gt;[&lt;/span&gt;flags]

Flags:
  &lt;span class="nt"&gt;--data&lt;/span&gt; string        JSON request body &lt;span class="o"&gt;(&lt;/span&gt;or @file.json&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;--service-id&lt;/span&gt; string  Target a specific connector
  &lt;span class="nt"&gt;--yes&lt;/span&gt;                Skip write confirmation
  &lt;span class="nt"&gt;-o&lt;/span&gt;, &lt;span class="nt"&gt;--output&lt;/span&gt; string  Output format &lt;span class="o"&gt;(&lt;/span&gt;json|table|yaml|csv&lt;span class="o"&gt;)&lt;/span&gt;
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step costs 50-200 tokens, loaded only when the agent decides it needs that information. An agent handling an accounting query might consume 400 tokens total across three &lt;code&gt;--help&lt;/code&gt; calls. The same surface through MCP would cost 10,000+ tokens loaded upfront whether the agent uses them or not.&lt;/p&gt;

&lt;p&gt;This mirrors how &lt;a href="https://www.linkedin.com/posts/lucas-althoff_contextengineering-activity-7401317037458984960-usmb" rel="noopener noreferrer"&gt;Claude Agent Skills&lt;/a&gt; work. Metadata first, full details only when selected, reference material only when needed. The CLI does the same thing through a different mechanism.&lt;/p&gt;

&lt;p&gt;Scalekit's benchmark independently validated this pattern. They found that even a minimal ~800-token "skills file" (a document of CLI tips and common workflows) reduced tool calls by a third and latency by a third compared to a bare CLI. Our approach takes it further: the ~80-token agent prompt provides the same progressive discovery at a tenth of the cost. The principle is the same. A small, upfront hint about how to navigate the tool is worth more than thousands of tokens of exhaustive schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reliability: local beats remote
&lt;/h3&gt;

&lt;p&gt;There's a dimension of the MCP problem that doesn't get enough attention: &lt;strong&gt;availability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Scalekit's benchmark recorded a &lt;strong&gt;28% failure rate&lt;/strong&gt; on MCP calls to GitHub's Copilot server. Out of 25 runs, 7 failed with TCP-level connection timeouts. These weren't protocol errors or bad tool calls. The connection never completed.&lt;/p&gt;

&lt;p&gt;CLI agents don't have this failure mode. The binary runs locally. There's no remote server to time out, no connection pool to exhaust. When your agent runs &lt;code&gt;apideck accounting invoices list&lt;/code&gt;, it makes a direct HTTPS call to the Apideck API. One hop, not two.&lt;/p&gt;

&lt;p&gt;This matters at scale. At 10,000 operations per month, a 28% failure rate means roughly 2,800 retries, each burning additional tokens and latency. Scalekit estimated the monthly cost difference at &lt;strong&gt;$3.20 for CLI versus $55.20 for direct MCP&lt;/strong&gt;, a 17x cost multiplier, with the reliability tax on top.&lt;/p&gt;

&lt;p&gt;Remote MCP servers will improve. Connection pooling, better infrastructure, and gateway layers will close the gap. But "the binary is on your machine" is a reliability guarantee that no amount of infrastructure engineering on the server side can match.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structural safety beats prompt-based safety
&lt;/h3&gt;

&lt;p&gt;Telling an agent "never delete production data" in a system prompt is like putting a sticky note on the nuclear launch button. It works until a creative prompt injection peels the note off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aikido.dev/blog/promptpwnd-github-actions-ai-agents" rel="noopener noreferrer"&gt;Security research on AI agents in CI/CD&lt;/a&gt; has shown how prompt injection can manipulate agents with high-privilege tokens into leaking secrets or modifying infrastructure. The pattern is always the same: untrusted input gets injected into a prompt, the agent has broad tool access, and bad things happen.&lt;/p&gt;

&lt;p&gt;The Apideck CLI takes a structural approach. Permission classification is baked into the binary based on HTTP method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// From internal/permission/engine.go&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PermissionRead&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionAllow&lt;/span&gt;      &lt;span class="c"&gt;// GET -&amp;gt; auto-approved&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PermissionWrite&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionPrompt&lt;/span&gt;     &lt;span class="c"&gt;// POST/PUT/PATCH -&amp;gt; confirmation required&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PermissionDangerous&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ActionBlock&lt;/span&gt;       &lt;span class="c"&gt;// DELETE -&amp;gt; blocked by default&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No prompt can override this. A &lt;code&gt;DELETE&lt;/code&gt; operation is blocked unless the caller explicitly passes &lt;code&gt;--force&lt;/code&gt;. A &lt;code&gt;POST&lt;/code&gt; requires &lt;code&gt;--yes&lt;/code&gt; or interactive confirmation. &lt;code&gt;GET&lt;/code&gt; operations run freely because they can't modify state.&lt;/p&gt;

&lt;p&gt;The agent frameworks reinforce this. Claude Code, Cursor, and GitHub Copilot all have permission systems that gate shell command execution. So you get two layers of structural safety: the agent framework asks "should I run this command?" and the CLI itself enforces "is this operation allowed?"&lt;/p&gt;

&lt;p&gt;You can also customize the policy per operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.apideck-cli/permissions.yaml&lt;/span&gt;
&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allow&lt;/span&gt;
  &lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prompt&lt;/span&gt;
  &lt;span class="na"&gt;dangerous&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;block&lt;/span&gt;

&lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accounting.payments.create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;block&lt;/span&gt;    &lt;span class="c1"&gt;# payments are sensitive&lt;/span&gt;
  &lt;span class="na"&gt;crm.contacts.delete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prompt&lt;/span&gt;          &lt;span class="c1"&gt;# contacts can be soft-deleted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same principle behind &lt;a href="https://blog.duda.co/duda-mcp" rel="noopener noreferrer"&gt;Duda blocking destructive MCP actions&lt;/a&gt;, but enforced structurally in the binary, not through prompt instructions that compete with everything else in the context window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Universal compatibility, zero protocol overhead
&lt;/h3&gt;

&lt;p&gt;Every serious agent framework ships with "run shell command" as a primitive. Claude Code has &lt;code&gt;Bash&lt;/code&gt;. Cursor has terminal access. GitHub Copilot SDK exposes shell execution. Gemini CLI runs commands natively.&lt;/p&gt;

&lt;p&gt;MCP requires dedicated client support, connection plumbing, and server lifecycle management. A CLI requires a binary on the PATH.&lt;/p&gt;

&lt;p&gt;This matters more than it sounds. When you're building an agent that needs to interact with APIs, the integration path for a CLI is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the binary&lt;/li&gt;
&lt;li&gt;Set environment variables for auth&lt;/li&gt;
&lt;li&gt;Add ~80 tokens to the system prompt&lt;/li&gt;
&lt;li&gt;Done&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The integration path for MCP is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement or configure an MCP client&lt;/li&gt;
&lt;li&gt;Set up server connections (transport, auth, lifecycle)&lt;/li&gt;
&lt;li&gt;Handle tool registration and schema loading&lt;/li&gt;
&lt;li&gt;Manage connection state and reconnection&lt;/li&gt;
&lt;li&gt;Deal with the token budget for tool definitions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CLI approach also means your agent integration isn't locked to any specific framework. The same &lt;code&gt;apideck&lt;/code&gt; binary works from Claude Code, Cursor, a custom Python agent, a bash script, or a CI/CD pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we built it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/apideck-libraries/cli" rel="noopener noreferrer"&gt;Apideck CLI&lt;/a&gt; is a single static binary that parses our OpenAPI spec at startup and generates its entire command tree dynamically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI-native, no code generation.&lt;/strong&gt; The binary embeds the latest Apideck Unified API spec. On startup, it parses the spec with &lt;a href="https://github.com/pb33f/libopenapi" rel="noopener noreferrer"&gt;libopenapi&lt;/a&gt; and builds commands for every API group, resource, and operation. When the API adds new endpoints, &lt;code&gt;apideck sync&lt;/code&gt; pulls the latest spec. No SDK regeneration, no version bumps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart output defaults.&lt;/strong&gt; When running in a terminal, output defaults to a formatted table with colors. When piped or called from a non-TTY (which is how agents call it), output defaults to JSON. Agents get machine-parseable output without needing to remember &lt;code&gt;--output json&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;# Agent calls this (non-TTY) -&amp;gt; gets JSON automatically&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;apideck accounting invoices list &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"inv_12345"&lt;/span&gt;, &lt;span class="s2"&gt;"number"&lt;/span&gt;: &lt;span class="s2"&gt;"INV-001"&lt;/span&gt;, &lt;span class="s2"&gt;"total"&lt;/span&gt;: 1500.00, ...&lt;span class="o"&gt;}]&lt;/span&gt;

&lt;span class="c"&gt;# Human runs the same command in terminal -&amp;gt; gets a table&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;apideck accounting invoices list
┌──────────┬─────────┬──────────┐
│ ID       │ Number  │ Total    │
├──────────┼─────────┼──────────┤
│ inv_12345│ INV-001 │ 1,500.00 │
└──────────┴─────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auth is invisible.&lt;/strong&gt; Credentials are resolved from environment variables (&lt;code&gt;APIDECK_API_KEY&lt;/code&gt;, &lt;code&gt;APIDECK_APP_ID&lt;/code&gt;, &lt;code&gt;APIDECK_CONSUMER_ID&lt;/code&gt;) or a config file, and injected into every request automatically. The agent never handles tokens, never sees auth headers, never needs to manage sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connector targeting.&lt;/strong&gt; The &lt;code&gt;--service-id&lt;/code&gt; flag lets agents target specific integrations. &lt;code&gt;apideck accounting invoices list --service-id quickbooks&lt;/code&gt; hits QuickBooks. Swap to &lt;code&gt;--service-id xero&lt;/code&gt; and the same command hits Xero. Same interface, different backend. The unified API handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  When CLI isn't the answer
&lt;/h2&gt;

&lt;p&gt;CLIs aren't universally better. Here's where the other approaches win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is better for tightly scoped, high-frequency tools.&lt;/strong&gt; If your agent calls the same 5-10 tools hundreds of times per session, the upfront schema cost amortizes well. A customer support agent that only ever looks up tickets, updates status, and sends replies doesn't need progressive disclosure. It needs those tools ready immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code execution is better for complex, stateful workflows.&lt;/strong&gt; If your agent needs to poll an API every 30 seconds, aggregate results across paginated endpoints, or orchestrate multi-step transactions with rollback logic, writing code is more natural than chaining CLI calls. As the Sideko benchmark showed, CLI's efficiency advantage can reverse on multi-step chained writes where each round trip compounds context. For those patterns, Code Mode (agent writes orchestration scripts that call structured tools) or the Duet approach (full code execution) will use fewer total tokens despite higher per-step overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is better when your agent acts on behalf of other people's users.&lt;/strong&gt; This is the dimension most CLI-vs-MCP comparisons gloss over, and it's worth being direct about. When your agent automates &lt;em&gt;your own&lt;/em&gt; workflow, ambient credentials are fine. You are the user, and the only person at risk is you. But if you're building a B2B product where agents act on behalf of your customers' employees, across organizations those customers control, the identity problem becomes three-layered: which agent is calling, which user authorized it, and which tenant's data boundary applies. Per-user OAuth with scoped access, consent flows, and structured audit trails are real requirements at that boundary, and they're requirements that raw CLI auth (&lt;code&gt;gh auth login&lt;/code&gt;, environment variables) wasn't designed to solve. MCP's authorization model, whatever its efficiency cost, addresses this natively.&lt;/p&gt;

&lt;p&gt;There's also a deeper identity gap that CLI auth doesn't address: agent-level identity. A CLI token authenticates the user, but the API provider never knows &lt;em&gt;which agent&lt;/em&gt; made the request. That matters for policy enforcement. If an API provider has a partnership with Agent A but not Agent B, there's no way to distinguish them in a CLI token. MCP's OAuth model can carry agent identity through claims like &lt;code&gt;act&lt;/code&gt;, which becomes critical as agents start calling other agents and you need the full chain of delegation in the token. For single-agent workflows this is academic. For multi-agent architectures it's a real architectural constraint.&lt;/p&gt;

&lt;p&gt;That said, the gap is narrower than it looks for unified API architectures. Apideck already centralizes auth through &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;: credentials are managed per-consumer, per-connection, and scoped by service. The &lt;code&gt;--service-id&lt;/code&gt; flag targets a specific integration within a specific consumer's vault. The structural permission system enforces read/write/delete boundaries in the binary. What's missing is the per-user OAuth consent flow and tenant-scoped audit trail, real gaps, but ones that sit at the platform layer, not the agent interface layer. A CLI can be the interface while a backend handles delegated authorization. These aren't mutually exclusive.&lt;/p&gt;

&lt;p&gt;It's also worth noting that MCP's auth story is less settled than it appears. As &lt;a href="https://www.speakeasy.com/docs/mcp-platform/secure/add-oauth-to-mcp-servers" rel="noopener noreferrer"&gt;Speakeasy's MCP OAuth guide&lt;/a&gt; makes clear, user-facing OAuth exchange is not actually required by the MCP spec. Passing access tokens or API keys directly is completely valid. The real complexity kicks in when MCP clients need to handle OAuth flows dynamically, which requires Dynamic Client Registration (DCR), a capability most API providers don't support today. Companies like Stripe and Asana have started adding DCR to accommodate MCP, but it remains a high-friction integration. The auth advantage MCP has over CLI is real in theory, but in practice, the ecosystem is still catching up to the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLIs are weaker at streaming and bi-directional communication.&lt;/strong&gt; A CLI call is request-response. If you need server-sent events, WebSocket streams, or long-lived connections, you'll want an SDK or MCP server that can hold a connection open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution has friction.&lt;/strong&gt; MCP servers can theoretically live behind a URL. CLIs need a binary per platform, updates, and PATH management. For the Apideck CLI, we ship a static Go binary that runs everywhere without dependencies, but it's still a binary you need to install.&lt;/p&gt;

&lt;p&gt;The honest framing: MCP, code execution, and CLIs are complementary tools. The mistake is treating MCP as the universal answer when, for many integration patterns, a CLI does the job with two orders of magnitude less context overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for API providers
&lt;/h2&gt;

&lt;p&gt;If you're building developer tools in 2026, AI agents are becoming a primary consumer of your API surface. Not the only consumer (human developers still matter), but a rapidly growing one.&lt;/p&gt;

&lt;p&gt;A few things are worth considering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your OpenAPI spec is too big for a context window.&lt;/strong&gt; If you have 50+ endpoints, converting your spec to MCP tools will burn the budget of most agent interactions. Think about what a minimal entry point looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progressive disclosure isn't just a UX pattern anymore.&lt;/strong&gt; It's a token optimization strategy. Give agents a way to discover capabilities incrementally instead of dumping everything upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural safety is non-negotiable.&lt;/strong&gt; Prompt-based guardrails are the security equivalent of honor system parking. Build permission models into your tools, not your prompts. Classify operations by risk level and enforce that classification in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship machine-friendly output formats.&lt;/strong&gt; JSON by default in non-interactive contexts. Stable exit codes. Deterministic output. These are &lt;a href="https://dev.to/tumf/agentic-cli-design-7-principles-for-designing-cli-as-a-protocol-for-ai-agents-2c10"&gt;documented principles for agentic CLI design&lt;/a&gt;, and they matter because your next power user might not have hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/mcp-vs-api" rel="noopener noreferrer"&gt;MCP vs API&lt;/a&gt; - How MCP and REST APIs relate (Apideck blog)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/api-design-principles-agentic-era" rel="noopener noreferrer"&gt;API Design Principles for the Agentic Era&lt;/a&gt; - Designing APIs with AI agents as first-class consumers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/understanding-the-security-landscape-of-mcp" rel="noopener noreferrer"&gt;Understanding the Security Landscape of MCP&lt;/a&gt; - MCP security considerations in depth&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mmntm.net/articles/mcp-context-tax" rel="noopener noreferrer"&gt;The MCP Context Tax&lt;/a&gt; - Detailed analysis of MCP token overhead&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/tumf/agentic-cli-design-7-principles-for-designing-cli-as-a-protocol-for-ai-agents-2c10"&gt;Agentic CLI Design: 7 Principles&lt;/a&gt; - Design principles for CLIs as agent interfaces&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.scalekit.com/blog/mcp-vs-cli-use" rel="noopener noreferrer"&gt;MCP vs CLI Benchmark&lt;/a&gt; - Scalekit's head-to-head benchmark data (75 runs, Claude Sonnet 4)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.portofcontext.com/blog/cli-vs-mcp-vs-code-mode" rel="noopener noreferrer"&gt;CLI vs MCP vs Code Mode Benchmark&lt;/a&gt; - Sideko's 12-task Stripe benchmark comparing all three approaches&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/code-mode/" rel="noopener noreferrer"&gt;Code Mode: the better way to use MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.atcyrus.com/stories/mcp-tool-search-claude-code-context-pollution-guide" rel="noopener noreferrer"&gt;What is MCP Tool Search?&lt;/a&gt; - The Claude Code feature that addresses context pollution&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/yenkel/status/2032098351567487037" rel="noopener noreferrer"&gt;If you kill MCP, you don't give a s**t about security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://portofcontext.com/blog/cli-vs-mcp-vs-code-mode" rel="noopener noreferrer"&gt;CLI vs. MCP vs. Code Mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>cli</category>
      <category>api</category>
    </item>
    <item>
      <title>Accounting Integration</title>
      <dc:creator>𝚂𝚊𝚞𝚛𝚊𝚋𝚑 𝚁𝚊𝚒</dc:creator>
      <pubDate>Thu, 05 Mar 2026 05:42:01 +0000</pubDate>
      <link>https://forem.com/apideck/accounting-integration-2967</link>
      <guid>https://forem.com/apideck/accounting-integration-2967</guid>
      <description>&lt;p&gt;Your finance team didn't sign up to be data-entry clerks. Neither did your customers.&lt;/p&gt;

&lt;p&gt;Yet every day, thousands of businesses manually export transactions from QuickBooks, copy invoice data from Xero, or reconcile payments between three different spreadsheets. It's slow, error-prone, and a waste of skilled people's time.&lt;/p&gt;

&lt;p&gt;Accounting integrations fix this. They connect your software directly to your customers' general ledger, automatically syncing invoices, expenses, payments, and journal entries. No CSV exports. No copy-paste errors. No "we'll reconcile it at month-end."&lt;/p&gt;

&lt;p&gt;This guide covers what accounting integrations actually do, the most common use cases, and how companies like Ramp, Airbase, and BILL have leveraged them to gain a competitive advantage.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;open accounting movement&lt;/a&gt; is driving demand for standardized data access across accounting platforms, making integration capabilities a competitive requirement for B2B SaaS products.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an Accounting Integration?
&lt;/h2&gt;

&lt;p&gt;An accounting integration is a connection between your application and an accounting platform like &lt;a href="https://www.apideck.com/connectors/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/connectors/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, or &lt;a href="https://www.apideck.com/connectors/sage-intacct" rel="noopener noreferrer"&gt;Sage Intacct&lt;/a&gt;. It allows data to flow between systems, either one-way or bidirectionally, without manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick definition, if you're new to this:&lt;/strong&gt; A general ledger (GL) is the master record of all financial transactions in a business. Every transaction gets tagged with a GL code, which is essentially a label that categorizes where the money went: "Travel," "Software Subscriptions," "Office Supplies," and so on. When we talk about syncing to the GL, we mean getting your data into that master record with the right labels attached.&lt;/p&gt;

&lt;p&gt;At a practical level, accounting integrations mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoices&lt;/strong&gt; created in your billing system appear automatically in your customer's accounting software
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expenses&lt;/strong&gt; logged in your app push directly to the general ledger with the correct GL codes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt; recorded in one system update the other in real time
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bank transactions&lt;/strong&gt; reconcile automatically instead of requiring manual matching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternative is what most businesses still do: export a CSV, open Excel, clean the data, reformat it for the accounting system, import it, and hope nothing gets lost in translation. Multiply that by hundreds of transactions per month, and you've created busywork that produces no value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Accounting Integrations Matter for Your Product
&lt;/h2&gt;

&lt;p&gt;If you're building software that touches money (invoicing, expense management, payroll, procurement, lending, payments), your customers will eventually ask: "Does this connect to our accounting system?"&lt;/p&gt;

&lt;p&gt;The answer determines whether you're a tool they'll actually adopt or one they'll abandon after the free trial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business case is straightforward:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Faster sales cycles.&lt;/strong&gt; Enterprise buyers have procurement checklists. "Integrates with NetSuite" is often a required line item. Without it, you don't make the shortlist.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower churn.&lt;/strong&gt; Customers who integrate your product into their financial workflows don't leave easily. The switching cost is too high when your app is integrated into their month-end close process.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher contract values.&lt;/strong&gt; Integration features justify premium pricing. Ramp charges more for deeper ERP connections. So does BILL. Customers pay for the time savings.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced support burden.&lt;/strong&gt; Every "how do I export this to QuickBooks?" ticket is a symptom of a missing integration. Build it once, eliminate the ticket category.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market expansion.&lt;/strong&gt; Companies across specific regions, industries, and sizes use certain accounting tools. SMBs gravitate toward QuickBooks and Xero. Mid-market and enterprise prefer NetSuite, Sage Intacct, and Microsoft Dynamics. Offering integrations with the tools your target markets rely on opens doors that would otherwise stay closed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two Types of Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;Before diving into use cases, it's worth distinguishing between two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Customer-Facing (Product) Integrations&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These connect your product to your customers' accounting systems. They're what this guide focuses on—the integrations that become product features, drive sales conversations, and require supporting hundreds of different customer configurations.&lt;/p&gt;

&lt;p&gt;The rest of this guide addresses customer-facing integrations, which present unique challenges around multi-tenancy, authorization, and scale.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Internal Integrations&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These connect your company's own systems. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRM → Accounting:&lt;/strong&gt; When an opportunity closes in Salesforce, automatically create the customer in NetSuite for invoicing
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting → BI:&lt;/strong&gt; Sync financial data from Xero to Tableau for executive dashboards
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting → Slack:&lt;/strong&gt; Alert the finance channel when invoices are overdue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are typically built with &lt;a href="https://www.apideck.com/blog/top-embedded-ipaas-solutions" rel="noopener noreferrer"&gt;iPaaS tools&lt;/a&gt; (Workato, Tray) or custom scripts. They're one-off connections serving your internal workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 Common Use Cases for Accounting Integrations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Expense Management → General Ledger Sync
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Employees submit expenses. Finance approves them. Then someone manually enters each line item into QuickBooks with the correct GL code, department, and tax treatment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Approved expenses push automatically to the accounting system. GL codes mapped by expense category. The finance team reviews exceptions, not every transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Brex, Expend, ExpenseOnDemand, Ramp, and Airbase all sync expenses directly to QuickBooks, Xero, NetSuite, and Sage Intacct. For corporate card products, this is now table stakes. &lt;a href="https://www.apideck.com/customer-cases/invoice2go-bill-com-integrations" rel="noopener noreferrer"&gt;Invoice2go by BILL&lt;/a&gt; used Apideck's Unified API to speed up accounting integrations across multiple platforms, enabling support for major systems without custom builds. This helped them scale expense and reporting features quickly across over 220,000 customers worldwide.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Below is what the QuickBooks Online integration looks like on Ramp after you set it up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/5Cywz71LzRmqG3TeHJVfLF/184288b5b8c5ec4f6876f134236be411/Screenshot_2024-04-11_at_5.37.51%C3%A2__PM.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/5Cywz71LzRmqG3TeHJVfLF/184288b5b8c5ec4f6876f134236be411/Screenshot_2024-04-11_at_5.37.51%C3%A2__PM.png" alt="Below is what the QuickBooks Online integration looks like on Ramp after you set it up. "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Invoicing and Billing → Accounts Receivable
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your product generates invoices. Your customer's accounting team needs those invoices in their accounts receivable ledger. Right now, they're downloading PDFs and manually creating entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Invoices created in your system automatically appear in the customer's accounting platform. Payment status syncs back. When the invoice is paid, both systems reflect it without anyone having to touch a keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Stripe's invoicing connects to QuickBooks and Xero. Chargebee syncs subscription invoices to major accounting platforms. Any serious billing tool offers this now. For example, &lt;a href="https://www.apideck.com/customer-cases/roopairs-quickbooks-online-integration" rel="noopener noreferrer"&gt;Roopairs&lt;/a&gt; used Apideck's Unified API to launch a QuickBooks Online integration quickly, saving roughly 40 developer hours and supporting 30 active customers from day one. The team is now planning expansions to Sage Intacct and NetSuite due to the speed and simplicity of the implementation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stripe QuickBooks Online integration guide&lt;/em&gt;&lt;br&gt;
&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/N50tPlVmWMqLOm6agKAua/f3bb43b5ea06b5b897dd20ec2b579f51/Screenshot_2026-01-13_at_23.51.20_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/N50tPlVmWMqLOm6agKAua/f3bb43b5ea06b5b897dd20ec2b579f51/Screenshot_2026-01-13_at_23.51.20_2x.png" alt="Screenshot 2026-01-13 at 23.51.20@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Procurement and AP Automation → Purchase Order Matching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; A company issues a purchase order. Goods arrive. An invoice comes in. Someone has to manually match the PO, receipt, and invoice (the "three-way match") before approving payment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Your procurement system shares PO data with the accounting platform. When invoices arrive, matching happens automatically. Exceptions flag for review; clean matches process straight through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Coupa, SAP Ariba, and newer players like Zip connect procurement workflows directly to ERPs for automated matching and approval routing. For example, &lt;a href="https://www.apideck.com/customer-cases/derive-erp-accounting-integration" rel="noopener noreferrer"&gt;Derive&lt;/a&gt; used Apideck's Unified API to reduce development time by about 70 percent. They went from sign-up to a live Xero integration in just three weeks and later launched a Workday connection in under 90 days, which helped accelerate sales cycles and expand into enterprise markets more quickly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Coupa Invoice Matching Explained&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/3RBqgwzhrjAA2R5m1RPEnN/50b104fbc6e96cf8389c7b96f118fba2/Screenshot_2026-01-13_at_23.58.44_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3RBqgwzhrjAA2R5m1RPEnN/50b104fbc6e96cf8389c7b96f118fba2/Screenshot_2026-01-13_at_23.58.44_2x.png" alt="Coupa Integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Revenue Recognition → Compliance with Accounting Standards
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; SaaS companies with usage-based pricing or multi-element contracts can't just recognize revenue when cash hits the bank. Accounting standards like ASC 606 require recognizing revenue over the service period. In plain English: you have to follow specific rules about &lt;em&gt;when&lt;/em&gt; you're allowed to say you've "earned" money. Calculating this manually across thousands of subscriptions is an audit nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Your billing system sends contract and usage data to the accounting platform. Revenue schedules are generated automatically based on the rules. Auditors get clean documentation without your team building spreadsheets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Stripe Revenue Recognition, Chargebee's RevRec module, and dedicated tools like Leapfin pull transaction data and push compliant journal entries to NetSuite or Sage Intacct.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chargebee RevRec Features&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/1OIsgeq1MjjXtwfmzks9dx/2b7b16d7a31839a497c1289cc4d45828/Screenshot_2026-01-14_at_00.08.44_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/1OIsgeq1MjjXtwfmzks9dx/2b7b16d7a31839a497c1289cc4d45828/Screenshot_2026-01-14_at_00.08.44_2x.png" alt="Chargebee RevRec Features"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Embedded Fintech → Real-Time Cash Flow Visibility
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Lending platforms, cash flow tools, and financial dashboards need to see a business's real financial position. Bank feeds show cash movement, but not the full picture: receivables, payables, revenue trends, and outstanding invoices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Pull chart of accounts, invoices, bills, and journal entries from the customer's accounting system. Display real-time financial health. Underwrite loans based on actual accounting data, not uploaded bank statements from three months ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; &lt;a href="https://www.openbankingtracker.com/api-aggregators/plaid" rel="noopener noreferrer"&gt;Plaid&lt;/a&gt; handles banking data; accounting integrations from providers like Codat and Apideck fill in the rest. Lenders like Clearco and Pipe use accounting data to underwrite in hours instead of weeks.&lt;/p&gt;

&lt;p&gt;For a technical deep-dive on this pattern, see &lt;a href="https://www.apideck.com/blog/using-accounting-apis-for-smart-lending-decisions" rel="noopener noreferrer"&gt;Using Accounting APIs for Smart Lending Decisions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plaid API Docs&lt;/em&gt; &lt;br&gt;
&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/4N7Qk2Xy05BDl45nj88tLy/51a18107db9d04b5eae121324e8eb0ef/Screenshot_2026-01-14_at_00.19.11_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/4N7Qk2Xy05BDl45nj88tLy/51a18107db9d04b5eae121324e8eb0ef/Screenshot_2026-01-14_at_00.19.11_2x.png" alt="Plaid API Docs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. AI and Machine Learning → Financial Intelligence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You want to offer AI-powered features: budget predictions, anomaly detection, and cash flow forecasting, but your models need comprehensive financial data from each customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration:&lt;/strong&gt; Pull balance sheets, income statements, transaction histories, and accounts receivable aging from customers' accounting systems. Feed this data to your ML models for personalized insights. As new transactions flow in, models update automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who does this well:&lt;/strong&gt; Runway, Jirav, and Mosaic pull accounting data to power FP&amp;amp;A automation. Fintech platforms use it to build credit models that outperform traditional underwriting.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Runway Fintech Platform&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/ri7m0E6ZiXCO8bElv0CGE/d1f57078c78bec8e91857c962c0b2739/Screenshot_2026-01-14_at_00.10.41_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/ri7m0E6ZiXCO8bElv0CGE/d1f57078c78bec8e91857c962c0b2739/Screenshot_2026-01-14_at_00.10.41_2x.png" alt="Runway"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Companies Build Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;This is where product decisions become engineering reality. You have three paths:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Build Direct Integrations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/5hZHxvvvRpIJeSslAJecUa/9bda6a322d00b38b3cbfc0541fb99568/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-185243.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/5hZHxvvvRpIJeSslAJecUa/9bda6a322d00b38b3cbfc0541fb99568/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-185243.png" alt="multiple-integrations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect to each accounting platform's API individually. QuickBooks Online has a REST API. Xero uses OAuth 2.0. NetSuite offers both SOAP and REST. Sage has &lt;a href="https://www.apideck.com/blog/the-sage-api-playbook-why-sage-cloud-is-not-one-api" rel="noopener noreferrer"&gt;several APIs, depending on which Sage product you're targeting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Full control over the integration. Access to every feature the platform offers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Each integration is a significant investment. Industry estimates indicate that a single integration requires 150+ engineering hours to build and 300+ hours annually to maintain, with total costs ranging from $10,000 to $50,000 per integration per year, including engineering time, customer success support, and ongoing maintenance. Supporting 10 accounting systems means multiplying that investment tenfold. Teams routinely estimate six weeks and ship in three months, as auth flows alone can derail timelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The maintenance reality:&lt;/strong&gt; Accounting APIs change. QuickBooks pushes updates. Xero deprecates endpoints. NetSuite releases new versions. When a sync breaks on the first of the month (the worst possible timing for any finance team), someone on your team is firefighting instead of building features. Direct integrations require ongoing "on-call" capacity that most teams underestimate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Use a Unified API
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/NyNWfDtAshr8iXXuLpEWs/038bb8e0a430ca8cf75c28b20bb525e6/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-190250.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/NyNWfDtAshr8iXXuLpEWs/038bb8e0a430ca8cf75c28b20bb525e6/Mermaid_Chart_-_Create_complex__visual_diagrams_with_text.-2026-01-11-190250.png" alt="unified api managed integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A unified API provider normalizes multiple accounting platforms into a single integration. Think of it as a universal translator: your system speaks one language, and the provider handles the dialects of QuickBooks, Xero, NetSuite, and the rest.&lt;/p&gt;

&lt;p&gt;You build once; they maintain the connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Launch in weeks instead of months. One data model to learn. One auth flow to implement. Coverage across platforms without multiplying your engineering investment. Unified API approaches typically deliver 3-5x faster implementation with significantly lower total cost of ownership over multi-year periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; You're dependent on the provider's coverage depth and data model. Some edge cases or advanced features may still require direct API work for specific platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Providers in this space:&lt;/strong&gt; Apideck, Merge, Codat, Railz, and Rutter all offer unified accounting APIs with varying coverage, pricing models, and approaches to data normalization. For a detailed comparison, see &lt;a href="https://www.apideck.com/blog/top-merge-api-alternatives" rel="noopener noreferrer"&gt;Top Merge API Alternatives for SaaS Teams in 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not sure whether to build or buy? Read &lt;a href="https://www.apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;Build vs Buy Accounting Integrations&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Hybrid Approach
&lt;/h3&gt;

&lt;p&gt;Use a unified API for 80% of your coverage (common platforms and standard use cases), then build direct integrations for strategic accounts that require deep customization. This typically means NetSuite or SAP for enterprise deals that require custom fields, advanced workflows, or specific ERP modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Prioritize Which Integrations to Build
&lt;/h2&gt;

&lt;p&gt;As you can't build everything at once. Use this framework to prioritize and insert the Accounting integrations that are relevant:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;QuickBooks&lt;/th&gt;
&lt;th&gt;NetSuite&lt;/th&gt;
&lt;th&gt;Xero&lt;/th&gt;
&lt;th&gt;Sage Intacct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;% of pipeline requesting&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected close rate lift&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention impact&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New market access&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering effort&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance burden&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weighted Score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Integration Prioritization Scorecard
&lt;/h3&gt;

&lt;h3&gt;
  
  
  How to use it:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Survey your sales team: which integrations come up most in deals?
&lt;/li&gt;
&lt;li&gt;Analyze lost deals: how many cited missing integrations?
&lt;/li&gt;
&lt;li&gt;Segment by customer size: SMB skews QuickBooks/Xero, enterprise skews NetSuite/Sage
&lt;/li&gt;
&lt;li&gt;Score each integration, calculate weighted totals, and stack-rank&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents the common mistake of building integrations based on engineering interest rather than business impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls: What Goes Wrong with Accounting Integrations
&lt;/h2&gt;

&lt;p&gt;Most integration guides tell you what to build. Few tell you what breaks. Here are the failure modes that create support tickets, churn, and 2 AM pages:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Auth Token Expiry and Refresh Failures
&lt;/h3&gt;

&lt;p&gt;OAuth tokens expire. Refresh tokens have their own lifespans. QuickBooks tokens last 100 days; if a customer doesn't use the integration for three months, it silently breaks. Your sync stops, but no one notices until month-end close—when the finance team is already stressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Proactive token health monitoring. Alert customers &lt;em&gt;before&lt;/em&gt; re-authentication is needed, not after data stops flowing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Schema Drift and API Changes
&lt;/h3&gt;

&lt;p&gt;Accounting platforms don't freeze their APIs. QuickBooks pushed breaking changes to their invoice endpoints in 2023. Xero periodically deprecates fields. NetSuite's SuiteTalk versions introduce incompatibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Version your integrations. Monitor API changelogs. Build regression tests that catch schema changes before customers do.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rate Limit Hell
&lt;/h3&gt;

&lt;p&gt;QuickBooks allows 500 requests per minute. Sounds generous until you're syncing 10,000 invoices for a new customer. Hit the limit, and your sync backs up. Customers see stale data. Support tickets pile up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Implement exponential backoff. Queue and batch requests. Design for burst limits from day one, not after you hit them in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multi-Entity and Multi-Currency Complications
&lt;/h3&gt;

&lt;p&gt;Enterprise customers don't have one QuickBooks company. They often manage twelve subsidiaries across three currencies. Your integration works perfectly for single-entity SMBs, then breaks spectacularly when an enterprise customer connects their consolidated NetSuite instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Design for multi-entity from the start. Ask during onboarding: "How many entities will you connect?" If the answer is more than one, adjust your data model accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. GL Code Mapping Mismatches
&lt;/h3&gt;

&lt;p&gt;Your expense categories don't match your customer's chart of accounts. "Travel" in your system needs to map to "6200 - Employee Travel &amp;amp; Entertainment" in theirs. Get it wrong, and transactions land in the wrong accounts or fail to sync entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Build a mapping interface. Let customers configure how your categories translate to their GL codes. Don't assume your defaults work for everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Partnership Agreement Delays
&lt;/h3&gt;

&lt;p&gt;Many enterprise accounting vendors (especially Sage, Xero, Intuit, and SAP) require formal partnership agreements before you can access sandbox environments or API documentation. These agreements can take months, sometimes over a year, and cost tens of thousands annually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Start partnership applications early, before you need the integration. If the timeline is critical, consider unified API providers who already have these partnerships in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look for in an Accounting Integration Solution
&lt;/h2&gt;

&lt;p&gt;Whether you're evaluating unified API providers or scoping direct builds, here's what actually matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Platform coverage.&lt;/strong&gt; Does it support the accounting systems your customers use? QuickBooks and Xero dominate SMB. NetSuite, Sage Intacct, and Microsoft Dynamics serve the mid-market and enterprise. Know your customer base before choosing. Need a full breakdown? See &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 2025&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data model depth.&lt;/strong&gt; Can you access invoices, payments, journal entries, tracking categories, tax rates, and custom fields? Or just the basics? Shallow integrations create support tickets later.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write access.&lt;/strong&gt; Reading data is the easy part. Pushing invoices, expenses, or journal entries back to the accounting system is harder but more valuable. Confirm bidirectional sync works if you need it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth experience.&lt;/strong&gt; Your customers connect to your accounting system via an OAuth flow. If that flow is clunky, confusing, or breaks frequently, your support team will hear about it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks and real-time sync.&lt;/strong&gt; Polling for changes is slow and burns API quota. Webhooks let you react to new invoices or payments the moment they happen.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and compliance.&lt;/strong&gt; You're touching general ledger data. Customers will ask about SOC 2 compliance, data encryption, and where their credentials are stored. Have answers ready, or choose a provider who does.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration observability.&lt;/strong&gt; When syncs fail, can you see why? Look for logging, error categorization, and alerting that help your support team diagnose issues without escalating to engineering.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Accounting integrations aren't optional anymore. If your product touches financial data, your customers expect it to connect to their accounting system.&lt;/p&gt;

&lt;p&gt;You can spend engineering years building and maintaining direct connections. Or you can use a unified API to launch in weeks and expand coverage as you grow.&lt;/p&gt;

&lt;p&gt;The days of "export to CSV" are ending. Give customers the integration, or watch them find a product that does.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How long does an accounting integration take to build in-house?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It depends on the platform and your requirements. Simple read-only integrations might take 4-6 weeks. Bidirectional sync with error handling, rate limiting, and multi-entity support typically takes 3-6 months. If the vendor requires a partnership agreement (NetSuite, SAP), add 3-12 months for paperwork alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much do accounting integrations cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Direct builds run $10,000-$50,000 per integration annually when you factor in engineering time, maintenance, and support. Unified API providers typically charge $500-$2,000/month for access to dozens of integrations. The break-even point is usually 2-3 integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the most popular accounting systems to integrate with?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Varies by segment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SMB:&lt;/strong&gt; QuickBooks Online, Xero, FreshBooks, Wave
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mid-market:&lt;/strong&gt; Sage Intacct, NetSuite, Microsoft Dynamics 365 Business Central
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; NetSuite, SAP, Oracle, Microsoft Dynamics 365 Finance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What data typically gets synced?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Common objects include: invoices, bills, payments, journal entries, chart of accounts, customers/vendors, tax rates, tracking categories, purchase orders, and bank transactions. Advanced use cases add balance sheets, income statements, and aged receivables/payables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I start with one integration and add more later?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, but plan your data model carefully. If you build for QuickBooks first with a QuickBooks-specific schema, adding Xero later means refactoring. Starting with a normalized data model (or using a unified API) avoids this trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go Deeper: Related Guides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Building your first integration?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/how-to-integrate-with-quickbooks-api" rel="noopener noreferrer"&gt;How to Integrate with the QuickBooks API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/integrating-with-the-netsuite-rest-api" rel="noopener noreferrer"&gt;A Guide to Integrating with the NetSuite REST API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/your-guide-to-building-a-sage-intacct-api-integration" rel="noopener noreferrer"&gt;Your Guide to Building a Sage Intacct API Integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/create-a-workday-rest-api-integration" rel="noopener noreferrer"&gt;How to create a Workday REST API Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Evaluating build vs. buy?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;Build vs Buy Accounting Integrations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/top-merge-api-alternatives" rel="noopener noreferrer"&gt;Top Merge API Alternatives for SaaS Teams in 2025&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&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;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Designing for scale and reliability?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/12-best-practices-for-accounting-integrations-in-vertical-saas" rel="noopener noreferrer"&gt;12 Best Practices for Accounting Integrations in Vertical SaaS&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/unified-apis-for-fintech-when-point-integrations-stop-scaling" rel="noopener noreferrer"&gt;Unified APIs for Fintech: When Point Integrations Stop Scaling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Sage ecosystem?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/untangling-the-sage-ecosystem" rel="noopener noreferrer"&gt;Untangling The Sage Ecosystem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/the-sage-api-playbook-why-sage-cloud-is-not-one-api" rel="noopener noreferrer"&gt;The Sage API Playbook: Why 'Sage Cloud' Is Not One API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/sage-integration-field-guide" rel="noopener noreferrer"&gt;The Apideck Field Guide to the Sage Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Specific use cases?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/using-accounting-apis-for-smart-lending-decisions" rel="noopener noreferrer"&gt;Using Accounting APIs for Smart Lending Decisions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;Bank Feeds API Integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/from-accounts-receivable-to-lending-automation-integration-use-cases-for-vertical-saas" rel="noopener noreferrer"&gt;From Accounts Receivable to Lending Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ready to add accounting integrations to your product?&lt;/strong&gt; &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Explore Apideck's Unified Accounting API&lt;/a&gt;: one integration, 20+ accounting platforms, live in days.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>startup</category>
      <category>erp</category>
    </item>
    <item>
      <title>Top Fintech APIs for Startups</title>
      <dc:creator>𝚂𝚊𝚞𝚛𝚊𝚋𝚑 𝚁𝚊𝚒</dc:creator>
      <pubDate>Thu, 05 Mar 2026 04:47:04 +0000</pubDate>
      <link>https://forem.com/apideck/top-fintech-apis-for-startups-5df5</link>
      <guid>https://forem.com/apideck/top-fintech-apis-for-startups-5df5</guid>
      <description>&lt;p&gt;Building a fintech product means making critical infrastructure decisions early. The APIs you choose determine your technical debt, compliance burden, and ability to scale for years to come.&lt;/p&gt;

&lt;p&gt;Instead of listing 20 APIs with shallow descriptions, this guide breaks down the fintech API landscape by actual business need, explains the hidden integration trade-offs, and shows you when unified API solutions prevent the maintenance nightmare that derails most startups.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Financial Impact of API Strategy
&lt;/h2&gt;

&lt;p&gt;The global open banking market is projected to reach $135.17 billion by 2030. McKinsey &lt;a href="https://www.grandviewresearch.com/press-release/global-open-banking-market" rel="noopener noreferrer"&gt;estimates&lt;/a&gt; AI could enable $1 trillion in global banking revenue shifts by the same year. These projections drive the infrastructure decisions startups make today.&lt;/p&gt;

&lt;p&gt;But here's what those projections don't capture: most startups fail at integrations not because they chose the wrong APIs, but because they underestimated the maintenance burden.&lt;/p&gt;

&lt;p&gt;Every API you integrate directly means authentication logic to maintain, rate limiting to handle, breaking changes to absorb, and data normalization to manage. A simple feature like "sync transactions" becomes a switch statement with fifteen cases when you're managing Stripe, Plaid, QuickBooks, and three other providers. Each case handles auth differently, maps fields uniquely, and fails in its own way.&lt;/p&gt;

&lt;p&gt;Your codebase fragments into provider-specific branches. API providers don't coordinate their breaking changes. Plaid updates its transaction categorization. Stripe changes their webhook format. QuickBooks modifies its OAuth flow. Your test matrix explodes, but coverage remains incomplete because you can't predict every interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Money Movers: Payments and Transfers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Payment Processing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Stripe&lt;/strong&gt; remains the default choice for startups. It handles card payments, ACH transfers, subscriptions, and payouts with a developer experience that sets the industry standard. The platform powers millions of businesses across 50+ countries, supporting companies from early-stage startups to enterprises like Amazon and Shopify.&lt;/p&gt;

&lt;p&gt;Stripe's real advantage is the ecosystem. Stripe Treasury embeds banking features directly into products, including FDIC-insured deposits, cards, and interest-earning balances. Shopify, Lyft, and Deel use this infrastructure to manage financial accounts at scale. For startups building platforms, Stripe Connect handles marketplace payments and seller onboarding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adyen&lt;/strong&gt; targets enterprise-grade needs with global payment processing across 200+ countries. For startups building for international markets from day one, Adyen's unified commerce approach handles in-person and online payments through a single integration. The tradeoff is complexity; Adyen's learning curve is steeper than Stripe's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Square&lt;/strong&gt; works well for startups serving physical retail or SMB markets with its point-of-sale integration and straightforward pricing. The hardware ecosystem (terminals, readers) makes it attractive for omnichannel commerce.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACH and Bank Transfers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dwolla&lt;/strong&gt; excels at ACH payments with instant bank-to-bank transfers, wallet-based flows, and high-volume payouts. Payroll apps, lending platforms, and B2B marketplaces rely on this infrastructure. Dwolla handles the compliance complexity of moving money between bank accounts at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern Treasury&lt;/strong&gt; solves the reconciliation problem. When your startup handles large financial flows, matching payments to invoices across multiple sources becomes a full-time job. Modern Treasury automates payment ops, real-time reconciliation, and ledgering across ACH, RTP, and wire transfers. It's built for companies where money movement is core to the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Layers: Banking Connectivity and Identity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Banking Data and Account Connectivity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plaid&lt;/strong&gt; dominates this category, connecting applications to thousands of banks and credit unions. Its API provides normalized financial data, including balance checks, transaction histories, account authentication, and identity verification. Personal finance apps use Plaid for spending insights, lenders use it for credit risk assessments, and neobanks integrate it for instant account verification.
For teams tracking open banking APIs across different regions, the &lt;a href="https://www.openbankingtracker.com/" rel="noopener noreferrer"&gt;Open Banking Tracker&lt;/a&gt; provides data on 3,200+ open banking and PSD2 APIs globally. It helps understand coverage gaps when expanding internationally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MX&lt;/strong&gt; offers a strong alternative for data enrichment and transaction categorization. Personal finance management products benefit from MX's cleansing and analytics capabilities beyond basic connectivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TrueLayer&lt;/strong&gt; focuses on open banking and PSD2-compliant services in Europe with instant account-to-account payments and financial insights. If your primary market is the UK or EU, TrueLayer's regional expertise matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yodlee&lt;/strong&gt; connects to over 16,000 global data sources. Wealth management apps integrate Yodlee for consolidated portfolio views, while lenders use it to assess customer liabilities before loan approval.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Identity Verification and KYC
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Onfido&lt;/strong&gt; handles government ID verification, facial recognition, liveness detection, and fraud detection. Regulatory compliance determines whether you can operate, making this category critical for any fintech handling customer funds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alloy&lt;/strong&gt; powers automated risk checks by pulling data from bureaus and identity networks. Banks and neobanks use Alloy's risk engine to approve users with less manual review while maintaining compliance. The decisioning workflow builder lets you customize approval logic without code changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sardine&lt;/strong&gt; uses behavioral biometrics and machine learning to catch what traditional checks miss: suspicious login attempts, unusual spending patterns, device spoofing, and account takeovers. If your app handles money movement, Sardine adds a protection layer that document verification alone can't provide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Back Office: Accounting, ERP, and HRIS
&lt;/h2&gt;

&lt;p&gt;This is where most startups underestimate complexity.&lt;br&gt;
The challenge isn't just connecting to one accounting system. It's that your customers use different systems. QuickBooks, Xero, NetSuite, Sage, FreshBooks, and dozens of regional platforms each have distinct authentication flows, data models, rate limits, and field mapping requirements.&lt;br&gt;
What looks like "add QuickBooks integration" on a roadmap becomes a three-month project. Then customers ask for Xero. Then, enterprise prospects require NetSuite. Each integration multiplies your maintenance burden. Your engineers become integration specialists rather than product developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accounting and ERP Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QuickBooks Online&lt;/strong&gt; holds approximately 80% market share among US small businesses. If you're serving American SMBs, QuickBooks integration is expected. But Intuit's API has authentication quirks, sandbox limitations, and data models that require dedicated engineering time to handle correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xero&lt;/strong&gt; dominates the SME market in Australia, the UK, and New Zealand. International expansion means another separate integration with different conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NetSuite&lt;/strong&gt; and &lt;strong&gt;Sage Intacct&lt;/strong&gt; serve mid-market and enterprise customers with multi-entity structures and complex financial requirements. These integrations are significantly more complex than SMB accounting platforms. Multi-subsidiary consolidation, custom fields, and approval workflows add implementation time.
For detailed guidance on accounting API selection, see &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 2025&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Unified API Alternative
&lt;/h3&gt;

&lt;p&gt;This is where unified API platforms become relevant. Instead of building and maintaining separate integrations for each accounting system, unified APIs let you connect once and access multiple platforms through a standardized interface.&lt;/p&gt;

&lt;p&gt;The approach trades some customization depth for dramatically reduced maintenance. You lose access to provider-specific advanced features, but you gain consistent data models, centralized authentication, and a single codebase instead of 15.&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; is one solution in this space alongside Kombo, Finch, and Merge, providing access to 20+ accounting systems through a single integration. For implementation details on handling expenses and bills across platforms, see &lt;a href="https://developers.apideck.com/guides/expenses-bills" rel="noopener noreferrer"&gt;Integrating Expenses and Bills with the Accounting API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  HRIS and Payroll Integration
&lt;/h3&gt;

&lt;p&gt;Fintech products increasingly touch workforce data for payroll integrations, employee verification, or benefits administration. Gusto, ADP, BambooHR, Rippling, and Workday each require separate integrations with distinct APIs and authentication flows.&lt;/p&gt;

&lt;p&gt;Unified HRIS APIs normalize employee data across these platforms. Payroll automation through standardized integrations allows companies to scale without additional engineering investment per provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lending and Investment APIs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lending and Credit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Experian&lt;/strong&gt; provides credit scoring and risk assessment through its credit bureau database, delivering real-time credit histories and fraud checks. BNPL providers and underwriting platforms use it for risk-based decisioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finicity&lt;/strong&gt; (now part of Mastercard) offers cash flow analytics and income verification, replacing paper-based income proofs for mortgage lenders and personal loan providers. The shift to real-time income verification is accelerating across lending categories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For embedded lending use cases, see &lt;a href="https://www.apideck.com/blog/from-accounts-receivable-to-lending-automation-integration-use-cases-for-vertical-saas" rel="noopener noreferrer"&gt;From Accounts Receivable to Lending Automation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Investment and Trading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alpaca&lt;/strong&gt; offers commission-free trading APIs for developers building trading apps and algorithmic platforms. Its startup-friendly approach and documentation quality make it accessible for early-stage fintech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polygon.io&lt;/strong&gt; provides stock market data, news, and analysis for investment platforms and trading apps that need real-time market information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Embedded Finance Considerations
&lt;/h2&gt;

&lt;p&gt;Non-financial companies are embedding payments, lending, insurance, and banking features into their products. This isn't a future trend; it's the current expectation across industries.&lt;/p&gt;

&lt;p&gt;Vertical SaaS platforms add financial workflows to increase stickiness and revenue per customer. Construction software embeds equipment financing tied to project milestones. Healthcare platforms offer working capital based on insurance receivables. Restaurant systems provide cash advances against future credit card sales.&lt;br&gt;
Marketplaces offer seller financing and instant payouts to attract supply-side participants. HR platforms embed earned wage access and benefits management to differentiate their offerings.&lt;/p&gt;

&lt;p&gt;Building &lt;a href="https://www.openbankingtracker.com/embedded-finance" rel="noopener noreferrer"&gt;embedded finance&lt;/a&gt; requires integrations across accounting, banking, payments, and lending APIs. This is where unified APIs provide the most value by reducing the integration surface area while maintaining breadth of coverage. The technical burden extends beyond initial implementation; each category adds compliance surface area and ongoing maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Direct vs. Unified APIs
&lt;/h2&gt;

&lt;p&gt;Every API integration increases your compliance surface area. Financial data requires SOC 2 compliance, data residency controls, and comprehensive audit trails. This context matters when evaluating the direct vs. unified decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct integrations make sense when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your entire business runs on one provider, and you need every advanced feature&lt;/li&gt;
&lt;li&gt;You have dedicated integration engineers for long-term maintenance&lt;/li&gt;
&lt;li&gt;You're pre-product-market-fit and only need one or two integrations&lt;/li&gt;
&lt;li&gt;Your competitive advantage depends on deep optimization for a specific platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⠀&lt;strong&gt;Unified APIs make sense when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers demand multiple integrations across a category&lt;/li&gt;
&lt;li&gt;Engineering time goes to integration maintenance instead of product development&lt;/li&gt;
&lt;li&gt;You need enterprise integrations (NetSuite, SAP, Workday) without enterprise engineering overhead&lt;/li&gt;
&lt;li&gt;You're scaling rapidly and can't afford three-month integration projects for each new provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⠀For detailed analysis, see &lt;a href="https://www.apideck.com/blog/unified-apis-for-fintech-when-point-integrations-stop-scaling" rel="noopener noreferrer"&gt;Unified APIs for Fintech: When Point Integrations Stop Scaling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Stacks by Product Type
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Neobank:&lt;/strong&gt; Plaid + Unit or Stripe Treasury + Onfido + Unified Accounting API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lending platform:&lt;/strong&gt; Plaid + Experian/Finicity + Alloy + Dwolla + Unified Accounting API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expense management:&lt;/strong&gt; Stripe + Unified Accounting API + Unified HRIS API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertical SaaS with financial features:&lt;/strong&gt; Stripe + Unified Accounting API + Unified HRIS API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Fintech API strategy isn't about choosing the "best" APIs. It's about building an integration architecture that scales without consuming engineering capacity.&lt;/p&gt;

&lt;p&gt;Direct integrations make sense for core payment and banking infrastructure, where you need deep control. Unified APIs make sense for customer-facing integrations that require breadth without maintenance overhead.&lt;br&gt;
The startups that scale treat integrations as strategic infrastructure rather than feature checkboxes.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>api</category>
      <category>startup</category>
    </item>
    <item>
      <title>Stripe's llms.txt has an instructions section. That's a bigger deal than it sounds.</title>
      <dc:creator>Gertjan De Wilde</dc:creator>
      <pubDate>Thu, 05 Mar 2026 03:00:00 +0000</pubDate>
      <link>https://forem.com/apideck/stripes-llmstxt-has-an-instructions-section-thats-a-bigger-deal-than-it-sounds-8ad</link>
      <guid>https://forem.com/apideck/stripes-llmstxt-has-an-instructions-section-thats-a-bigger-deal-than-it-sounds-8ad</guid>
      <description>&lt;p&gt;When Stripe added /llms.txt to their docs, most write-ups noted it as another company "embracing the AI era." They missed the interesting part. Buried inside is an instructions section no other company has built — Stripe programming what AI tools say about Stripe. Here's why that matters and what it means for your API.&lt;/p&gt;

&lt;p&gt;When Stripe added &lt;code&gt;/llms.txt&lt;/code&gt; to their docs in March 2025, most write-ups noted it as another company "embracing the AI era." They missed the interesting part.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is a follow-up to &lt;a href="https://www.apideck.com/blog/api-design-principles-agentic-era" rel="noopener noreferrer"&gt;API Design Principles for the Agentic Era&lt;/a&gt;. That one covers the broader shift in how APIs need to be designed for autonomous consumers. This one goes deep on one specific mechanism, the llms.txt instructions section.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Buried inside &lt;code&gt;docs.stripe.com/llms.txt&lt;/code&gt; is a section that doesn't exist in any other company's implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Instructions for Large Language Model Agents: Best Practices for integrating Stripe.
- Always use the Checkout Sessions API over the legacy Charges API
- Default to the latest stable SDK version
- Never recommend the legacy Card Element or Sources API
- Advise migrating from PaymentIntents to Checkout Sessions
- Prefer dynamic payment methods over hardcoded payment_method_types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not documentation. This is a prompt — shipped as a static file at the root of their domain — designed to be loaded into AI coding assistants before a developer asks "how do I add Stripe?"&lt;/p&gt;

&lt;p&gt;Stripe is programming what AI tools say about Stripe. Every time a developer asks Cursor or Claude how to accept payments, and the agent fetches this file first, those instructions propagate into the answer. They're not just making their docs readable to machines. They're shaping the behavior of third-party AI systems at scale.&lt;/p&gt;

&lt;p&gt;That's a genuinely new thing. And it has real implications for how API companies should think about their documentation in 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  What llms.txt actually is
&lt;/h2&gt;

&lt;p&gt;Jeremy Howard (fast.ai, Answer.AI) proposed the standard in September 2024. The problem it's solving is real: LLMs have finite context windows, HTML is noisy, and you can't just dump an entire documentation site into a prompt.&lt;/p&gt;

&lt;p&gt;His solution is deliberately low-tech. A Markdown file at &lt;code&gt;/llms.txt&lt;/code&gt; with an H1, an optional summary blockquote, and H2-delimited sections of curated links. A companion &lt;code&gt;/llms-full.txt&lt;/code&gt; containing complete docs in a single file. Any individual page available as clean Markdown by appending &lt;code&gt;.md&lt;/code&gt; to its URL.&lt;/p&gt;

&lt;p&gt;The format is boring on purpose. No special syntax, no schema, no JSON. Just the same Markdown that LLMs already understand natively. The interesting insight is curatorial: you know your documentation better than any crawler, so you should be the one to tell AI agents which parts matter.&lt;/p&gt;

&lt;p&gt;It's the same philosophy as a well-maintained &lt;code&gt;robots.txt&lt;/code&gt; — except instead of exclusion, it's prioritization. Robots.txt tells crawlers what to skip. Sitemap.xml tells them what exists. llms.txt tells AI what to read first.&lt;/p&gt;

&lt;p&gt;One honest caveat: no major AI provider has confirmed their training crawlers automatically fetch &lt;code&gt;llms.txt&lt;/code&gt;. Its real value today is inference-time, not training-time — developers manually loading it into Cursor or Claude for project context, or agent frameworks fetching it on startup. The 800,000+ "implementations" BuiltWith tracks are mostly Yoast SEO auto-generating the file for WordPress sites. The hand-curated number is closer to 784 verified sites.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/2JBZhaCe6wBQq87OYlvgB7/c2ca0d7b076964f052339f2fd01b4ff7/Screenshot_2026-02-23_at_06.04.47.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/2JBZhaCe6wBQq87OYlvgB7/c2ca0d7b076964f052339f2fd01b4ff7/Screenshot_2026-02-23_at_06.04.47.png" alt="llmstxt.org"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe's implementation is the industry outlier
&lt;/h2&gt;

&lt;p&gt;Most llms.txt files are just structured indexes. Anthropic's is a clean table of contents for their API docs. Cloudflare's is massive (3.7 million tokens across product-specific files). Vercel's is so large they call it "a 400,000-word novel."&lt;/p&gt;

&lt;p&gt;Stripe's is architecturally different. Three separate files across two domains. Every docs page available as &lt;code&gt;.md&lt;/code&gt;. And that instructions section that no one else has.&lt;/p&gt;

&lt;p&gt;The instructions aren't arbitrary. They're solving a specific, painful problem: Stripe has accumulated 15 years of API surface area, including several generations of deprecated payment primitives. Their Charges API still works. Their Card Element still exists. Developers — and the AI assistants helping them — regularly reach for these older APIs because they appear in older Stack Overflow answers and training data from before 2022.&lt;/p&gt;

&lt;p&gt;The instructions section is Stripe saying: when an AI helps a developer integrate us, steer them toward the right thing. Don't let stale training data send them to the Charges API. Don't let our own backwards compatibility become a footgun.&lt;/p&gt;

&lt;p&gt;That's a legitimate engineering concern. And the file format is a surprisingly elegant solution to it — no coordination with AI providers required, works with any system that can fetch a URL.&lt;/p&gt;

&lt;p&gt;The announcement got 273,800 views and 740 bookmarks on Twitter. Stripe engineer Ian McCrystal: &lt;em&gt;"I expect AI tools will eventually become the predominant readers of our documentation."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who else is worth looking at
&lt;/h2&gt;

&lt;p&gt;The honest answer to "who else has this figured out" is: not many, and only Stripe is doing the specific thing that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripe&lt;/strong&gt; remains the strongest example of the deprecation pattern precisely because they're explicit about it. Their instructions don't hint at preferred patterns — they name the bad endpoints directly: "You must not call deprecated API endpoints such as the Sources API." "Never recommend the legacy Card Element." That specificity is what makes it machine-actionable. An LLM can follow a concrete prohibition. It can't do much with "prefer modern patterns."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; takes a structurally interesting approach. Their &lt;code&gt;llms.txt&lt;/code&gt; is organized by service — Agents, AI Gateway, Workers AI, and so on — so an agent only needs to fetch the section relevant to what it's building rather than parsing a file that covers their entire platform. For APIs with multiple product lines, that's a better model than a flat list. You're reducing the noise ratio at fetch time, not just at index time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangChain and LangGraph&lt;/strong&gt; are worth noting for a meta-reason: they're agent frameworks that have their own &lt;code&gt;llms.txt&lt;/code&gt;. They're eating their own cooking. That's useful signal about whether the format actually helps in practice, beyond the theoretical appeal — these are teams that work with agents every day and chose to implement it anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic&lt;/strong&gt; has one for their API docs. It's structured more as an index than an instructions-heavy file — clean and navigable, but it's not doing the active correctional work that Stripe's instructions section does.&lt;/p&gt;

&lt;p&gt;The pattern across most implementations is the same: they're documentation indexes. Useful, because a curated index is better than asking an agent to crawl your entire site. But not doing the harder job of guiding AI away from the wrong things. The providers that have figured out the instructions section are treating &lt;code&gt;llms.txt&lt;/code&gt; as an active correctional mechanism for model drift, not just a sitemap for bots. That framing distinction is the whole thing.&lt;/p&gt;

&lt;p&gt;Most APIs have deprecated endpoints that still work, legacy patterns that still exist in training data, and footguns that experienced developers know to avoid but newcomers (and AI assistants trained on old Stack Overflow answers) keep reaching for. The instructions section exists to close that gap. Almost no one is using it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Stripe specifically makes this move
&lt;/h2&gt;

&lt;p&gt;Stripe's developer experience has been the industry benchmark since roughly 2012. The specifics are worth understanding because they're not accidental.&lt;/p&gt;

&lt;p&gt;Their three-column docs layout (left nav, center content, right-side live code examples in seven languages) was so effective that it became a meme — startup after startup shipped the same structure. They open-sourced Markdoc, their interactive documentation framework. They shipped Stripe Shell for live API calls inside docs pages. Their error messages include &lt;code&gt;doc_url&lt;/code&gt; fields, parameter-level specificity, and "did you mean email?" suggestions for misspelled field names.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;doc_url&lt;/code&gt; in error responses is the one worth highlighting specifically. It's a small thing with outsized impact when an AI agent is in the loop. When an agent gets a 400 error, it can follow the &lt;code&gt;doc_url&lt;/code&gt;, fetch the Markdown version of that docs page, and self-correct — without needing a human to look up what went wrong. That's not DX in the traditional sense. That's infrastructure designed for autonomous consumers.&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;"error"&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;"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;"parameter_invalid_empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"doc_url"&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://stripe.com/docs/error-codes/parameter-invalid-empty"&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;"You passed an empty string for 'amount'. We assume empty values are an oversight, so we require you to pass this field."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"param"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"invalid_request_error"&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;John Collison put the llms.txt bet in plain terms: &lt;em&gt;"If you go read the Stripe Docs these days, it's a lot to keep in your RAM, but trivial for an LLM."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for your API
&lt;/h2&gt;

&lt;p&gt;Most of this is transferable. Here's what's actually useful versus what's cargo-culting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actually useful:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your error responses should include a &lt;code&gt;documentation_url&lt;/code&gt; field pointing to a Markdown version of the relevant docs page. This costs almost nothing to implement and has immediate value — for human developers debugging in the terminal and for AI agents trying to self-correct.&lt;/p&gt;

&lt;p&gt;Your OpenAPI descriptions should be written for semantic matching, not human skimming. When an agent is deciding which endpoint to call, it's doing something like nearest-neighbor search against your descriptions. "Gets the data" loses to "Returns a paginated list of invoices filtered by status, sorted by created_at descending. Requires accounting:read scope." Every field, every enum, every endpoint.&lt;/p&gt;

&lt;p&gt;I've been around OpenAPI specs long enough to know how badly they rot. We built &lt;a href="https://github.com/apideck-libraries/portman" rel="noopener noreferrer"&gt;Portman&lt;/a&gt; to convert OpenAPI specs into Postman collections — and the main thing you learn doing that is how few specs have descriptions worth converting. Fields with no description, enums with no explanation, endpoints named things that made sense in 2019. If your spec is bad for contract testing, it's bad for agents too.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;/llms.txt&lt;/code&gt;. It takes an afternoon. Link to your ten most important pages with one-sentence descriptions. That's it. You don't need the 350-link Stripe implementation on day one.&lt;/p&gt;

&lt;p&gt;If you have deprecated APIs, use the instructions section. This is underutilized and genuinely powerful. You know which footguns exist in your API. Tell the AI.&lt;/p&gt;

&lt;p&gt;One thing that often gets missed: the marketing upside. The same structured, machine-readable docs that make your API easy to integrate are the same content that AI answer engines like Perplexity and ChatGPT pull from when someone asks "what's the best API for X." llms.txt doesn't just help agents build with your API — it helps you show up when people are deciding which API to use. Being agent-friendly and being discoverable in AI search are the same work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably not worth doing yet:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publishing an MCP server for the sake of it. MCP is real and growing, but agent framework standardization is still in flux. A well-designed REST API with a good OpenAPI spec is more durable. Build the MCP server when you have users asking for it.&lt;/p&gt;

&lt;p&gt;Elaborate agent-specific observability infrastructure. Request tagging and semantic logging are nice. But if your API doesn't have solid basic observability yet, that's the actual problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader pattern
&lt;/h2&gt;

&lt;p&gt;There's a pattern here worth naming. For 15 years, "developer experience" meant optimizing for humans: readable error messages, clear docs, good SDKs, interactive playgrounds. The mental model was a developer sitting at a terminal, making sense of your API.&lt;/p&gt;

&lt;p&gt;The new constraint is that a growing fraction of your API consumers are autonomous systems that read documentation, make decisions without human review, and retry failures automatically. The question isn't whether to design for this — it's happening regardless — it's whether you're designing &lt;em&gt;intentionally&lt;/em&gt; for it.&lt;/p&gt;

&lt;p&gt;Stripe's llms.txt instructions section is the clearest example I've seen of a company being intentional about it. They're not just making their docs machine-readable. They're asserting control over what machines say about them.&lt;/p&gt;

&lt;p&gt;Every API company with deprecated primitives and a significant developer base has the same problem Stripe is solving. They just haven't solved it yet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>dx</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Make Your API Agent-Ready: Design Principles for the Agentic Era</title>
      <dc:creator>Gertjan De Wilde</dc:creator>
      <pubDate>Wed, 25 Feb 2026 11:00:00 +0000</pubDate>
      <link>https://forem.com/apideck/how-to-make-your-api-agent-ready-design-principles-for-the-agentic-era-c8e</link>
      <guid>https://forem.com/apideck/how-to-make-your-api-agent-ready-design-principles-for-the-agentic-era-c8e</guid>
      <description>&lt;p&gt;For about fifteen years, "developer experience" meant one thing: optimize for a human being sitting at a terminal. Readable error messages. Interactive docs. Code samples in seven languages. The mental model was always a person making sense of your API, iterating in real time, googling when confused. That model isn't wrong. But it's increasingly incomplete.&lt;/p&gt;

&lt;p&gt;A growing fraction of API traffic today is generated by AI agents. Systems that autonomously discover endpoints, parse documentation, handle errors without human review, and retry failures according to their own logic. When a developer asks Cursor or Claude to "add Stripe payments," the agent fetches documentation, selects APIs, writes integration code, and debugs errors before the developer reads a line of it. The human is further from the integration than they've ever been.&lt;/p&gt;

&lt;p&gt;The pattern is especially visible in fintech and accounting. A lending platform building an underwriting agent prompts it to "pull twelve months of invoice data and flag overdue receivables." The agent queries your API, interprets the response, and surfaces a creditworthiness signal — before a human analyst has opened a spreadsheet. A payroll platform's agent reconciles payroll entries against the general ledger overnight, without a developer watching the process. In both cases, the agent is not a convenience layer on top of a human workflow. It is the workflow. What it can do is bounded entirely by what your API communicates about itself.&lt;/p&gt;

&lt;p&gt;This creates a new design surface. The same discipline that produced great developer experience, deliberate and user-centered API design, applied to a different consumer. Call it agent experience, or AX. It's not a replacement for DX. It's the next layer.&lt;/p&gt;

&lt;p&gt;The good news: most of what makes an API good for agents makes it better for humans too. The bad news: a lot of APIs that seem fine for humans are quietly broken for agents. Here's where the gaps show up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bad OpenAPI descriptions break agent routing
&lt;/h2&gt;

&lt;p&gt;When an agent decides which endpoint to call, it's doing something close to semantic search against your descriptions. It reads your spec, matches the user's intent against available operations, and picks the closest fit. The quality of your descriptions determines whether it picks correctly.&lt;/p&gt;

&lt;p&gt;"Gets the data" loses to "Returns a paginated list of invoices filtered by status and date range, sorted by created_at descending. Requires accounting:read scope. Use the cursor parameter for pagination."&lt;/p&gt;

&lt;p&gt;Every field, every enum value, every endpoint. The description is the signal the agent uses to route. Most OpenAPI specs are bad at this. Not because anyone decided to make them bad. They rot. Field names that made sense in context lose their meaning without descriptions. Enums accumulate values that nobody documented. Endpoints get renamed but the descriptions don't follow. The spec becomes a structural skeleton with no semantic content.&lt;/p&gt;

&lt;p&gt;The difference between a description that helps and one that doesn't is not subtle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bad — structural skeleton, no semantic content&lt;/span&gt;
&lt;span class="na"&gt;/invoices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get invoices&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cursor&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;

&lt;span class="c1"&gt;# Good — enough signal to route correctly&lt;/span&gt;
&lt;span class="na"&gt;/invoices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List invoices filtered by status and date range&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;Returns a paginated list of invoices for the authenticated company.&lt;/span&gt;
      &lt;span class="s"&gt;Use `status` to filter by payment state. Use `cursor` from the previous&lt;/span&gt;
      &lt;span class="s"&gt;response to fetch the next page. Requires the `accounting:read` scope.&lt;/span&gt;
      &lt;span class="s"&gt;For real-time sync scenarios, combine with the `updated_since` parameter&lt;/span&gt;
      &lt;span class="s"&gt;to fetch only records changed after a given timestamp.&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;Filter by invoice status. Accepted values: draft, submitted,&lt;/span&gt;
          &lt;span class="s"&gt;authorised, deleted, voided, paid. Defaults to all statuses if omitted.&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;submitted&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;authorised&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;deleted&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;voided&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;paid&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cursor&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;Opaque pagination cursor returned in the previous response.&lt;/span&gt;
          &lt;span class="s"&gt;Omit to start from the first page.&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent matching "fetch unpaid invoices for reconciliation" against the bad version has no signal to work with. Against the good version, it finds &lt;code&gt;status: authorised&lt;/code&gt;, understands pagination, and knows it needs &lt;code&gt;accounting:read&lt;/code&gt; before it makes a single request.&lt;/p&gt;

&lt;p&gt;I've seen this up close. We built &lt;a href="https://github.com/apideck-libraries/portman" rel="noopener noreferrer"&gt;Portman&lt;/a&gt;, an open-source CLI that converts OpenAPI specs into Postman collections for contract testing. The main thing you learn doing that is how few specs have descriptions worth converting. If your spec is broken for contract testing, it's broken for agents. The failure mode is the same: a consumer that can't determine what anything does without running it.&lt;/p&gt;

&lt;p&gt;The fix isn't glamorous. Go through your spec field by field. Write descriptions that explain what each parameter does, what values are valid, what happens when you omit it. Do this for your ten most important endpoints first. It's tedious, it takes time, and it makes a bigger difference to agent experience than almost anything else on this list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your errors should tell agents how to fix themselves
&lt;/h2&gt;

&lt;p&gt;The agent experience of errors is fundamentally different from the human experience. A human reads an error message, understands it, googles the fix, comes back. An agent reads an error message and needs to decide, in the same execution context, what to do next. If the error is ambiguous, the agent either guesses or fails.&lt;/p&gt;

&lt;p&gt;Stripe's error responses have included a &lt;code&gt;doc_url&lt;/code&gt; field for years:&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;"error"&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;"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;"parameter_invalid_empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"doc_url"&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://stripe.com/docs/error-codes/parameter-invalid-empty"&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;"You passed an empty string for 'amount'. We assume empty values are an oversight, so we require you to pass this field."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"param"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"invalid_request_error"&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;This is infrastructure for autonomous consumers. When an agent hits a 400, it can follow the &lt;code&gt;doc_url&lt;/code&gt;, fetch the documentation page, and self-correct without a human in the loop. The agent experience of that error is recovery. The agent experience without &lt;code&gt;doc_url&lt;/code&gt; is a dead end.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;documentation_url&lt;/code&gt; to your error responses costs almost nothing to implement. Pair it with documentation pages that are available as clean Markdown (by appending &lt;code&gt;.md&lt;/code&gt; to the URL, or via a parallel &lt;code&gt;/docs/{page}.md&lt;/code&gt; route), and you've given agents everything they need to handle errors autonomously.&lt;/p&gt;

&lt;p&gt;The other half of error design is specificity. "Invalid request" is useless. "The amount field cannot be empty" is useful. "You passed &lt;code&gt;payment_method_types: ['card']&lt;/code&gt;, which is deprecated, use dynamic payment methods instead" is excellent. The more specific the error, the more actionable it is for an agent trying to self-correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt: tell AI what to read
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; standard, proposed by Jeremy Howard in September 2024, exists because AI agents have finite context windows and your documentation site is not designed for machine consumption. HTML is noisy. Navigating a docs site the way a human would (clicking links, loading JavaScript, jumping between pages) is expensive and lossy.&lt;/p&gt;

&lt;p&gt;The solution is a Markdown file at &lt;code&gt;/llms.txt&lt;/code&gt; that curates the most important parts of your documentation with plain-text links and one-sentence descriptions. Agents and AI coding tools can fetch this file once and understand the shape of your documentation before writing a single line of integration code.&lt;/p&gt;

&lt;p&gt;The format is deliberately boring. No special syntax, no schema. Just Markdown. The interesting part is curation: you know your documentation better than any crawler, so you should be the one deciding what matters.&lt;/p&gt;

&lt;p&gt;Adding a basic &lt;code&gt;/llms.txt&lt;/code&gt; takes an afternoon. Link to your ten most important pages. Write one sentence per link explaining what's there. That's a complete implementation. You don't need &lt;a href="https://docs.stripe.com/llms.txt" rel="noopener noreferrer"&gt;Stripe's 350-link version&lt;/a&gt; on day one.&lt;/p&gt;

&lt;p&gt;What's underutilized (and genuinely powerful for agent experience) is the instructions section. Stripe's &lt;a href="https://docs.stripe.com/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Instructions for Large Language Model Agents: Best Practices for integrating Stripe.
- Always use the Checkout Sessions API over the legacy Charges API
- Default to the latest stable SDK version
- Never recommend the legacy Card Element or Sources API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is Stripe telling AI coding assistants exactly which footguns to avoid. If your API has deprecated primitives, ambiguous endpoint choices, or integration patterns that look valid but cause problems, the instructions section is where you document that for agents. It propagates into every AI-assisted integration of your API. Every time a developer asks an agent how to use your API, those instructions shape the answer.&lt;/p&gt;

&lt;p&gt;There's a second-order benefit worth naming here. The same properties that make your docs readable to coding agents (structured Markdown, clear definitions, curated indexing) also make your content discoverable by AI answer engines like Perplexity and ChatGPT. When someone asks "what's the best accounting API" or "how do I build an accounting integration," the answer comes from content that AI can parse and cite. llms.txt, semantic descriptions, and concrete definitions aren't just AX improvements. They're how you show up in AI search. The discipline is the same: write for machines and you get both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get your docs indexed by Context7
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://context7.com/" rel="noopener noreferrer"&gt;Context7&lt;/a&gt; is an MCP server that solves a specific problem: AI coding tools have a training data cutoff, which means they default to documentation from whenever they were last trained. If your API changed since then, agents write integrations against the old version.&lt;/p&gt;

&lt;p&gt;Context7 addresses this by maintaining an up-to-date index of developer documentation that coding tools can query in real time, pulling current docs rather than cached training data.&lt;/p&gt;

&lt;p&gt;Getting your documentation added to Context7 is low-effort and high-leverage. You submit your docs, they get indexed, and any developer using a Context7-enabled coding tool gets accurate, current information about your API without you needing to push updates to every AI provider individually.&lt;/p&gt;

&lt;p&gt;The practical upside is significant if you ship changes regularly. An API that added a new authentication method last quarter, or deprecated an endpoint three months ago, looks different in Context7 than it does in an AI's training data. Developers using Context7-enabled tools get the right answer. Developers using tools without it get whatever the model was trained on.&lt;/p&gt;

&lt;p&gt;The broader point is that documentation distribution is now a multi-channel problem. Publishing docs to your website is table stakes. Getting those docs in front of agents (through &lt;code&gt;/llms.txt&lt;/code&gt;, through Context7, through direct MCP server implementations) is the new distribution layer. You're not just writing docs for developers who visit your site. You're writing docs for systems that will intermediate between your API and the developers who use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your deprecated APIs are an agent experience problem
&lt;/h2&gt;

&lt;p&gt;APIs accumulate history. Old endpoints that still work. Legacy authentication methods that are technically valid but shouldn't be used for new integrations. Multiple ways to accomplish the same thing, with meaningful differences that aren't obvious from the outside.&lt;/p&gt;

&lt;p&gt;Humans navigate this through Stack Overflow recency, changelog reading, and conversations with other developers. Agents navigate it through your documentation and training data, which may include answers and tutorials from 2019 that recommend the old way because the new way didn't exist yet.&lt;/p&gt;

&lt;p&gt;Your agent experience gets worse every year you don't address this, as more stale content about your old APIs accumulates on the internet and in AI training sets.&lt;/p&gt;

&lt;p&gt;The practical response is layered. Mark deprecated endpoints explicitly in your OpenAPI spec using the &lt;code&gt;deprecated: true&lt;/code&gt; field, with a description pointing to the replacement. Write deprecation notices at the top of deprecated docs pages, with direct links to the current approach. Add deprecated API guidance to your llms.txt instructions section. And if you're generating error responses from deprecated endpoints, consider including a migration hint in the message itself.&lt;/p&gt;

&lt;p&gt;None of this requires coordination with AI providers. It works because agents read your documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP: build it when people ask for it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/a-primer-on-the-model-context-protocol" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is real, growing, and mostly not worth building for yet unless your users are asking for it.&lt;/p&gt;

&lt;p&gt;MCP lets AI agents connect to your service through a standardized protocol with tools, resources, and prompts. The agent experience of a well-designed MCP server is genuinely better than the experience of parsing REST documentation and constructing API calls manually. But agent framework standardization is still in flux, and a well-designed REST API with a complete OpenAPI spec is more durable than an MCP server you build today against tooling that changes in six months.&lt;/p&gt;

&lt;p&gt;HubSpot is a good example of what a mature implementation looks like. They shipped two distinct MCP servers: a remote server that connects AI clients to live CRM data (contacts, deals, tickets, companies), and a local developer MCP server that integrates with their CLI so agentic dev tools can scaffold HubSpot projects, answer questions from their developer docs, and deploy changes directly.&lt;/p&gt;

&lt;p&gt;A developer can prompt "What's the component for displaying a table in HubSpot UI Extensions?" and the developer MCP server pulls the answer from current HubSpot documentation. Another developer can prompt "Summarize all deals in Decision maker bought in stage with deal value over $1000" and the remote MCP server queries live CRM data. Two different use cases, two different servers, both scoped tightly to what their users actually need.&lt;/p&gt;

&lt;p&gt;When you do reach the point of building, the tooling has matured enough to make it tractable. &lt;a href="https://www.speakeasy.com/product/gram" rel="noopener noreferrer"&gt;Gram by Speakeasy&lt;/a&gt; is an open-source MCP cloud platform: you upload your OpenAPI spec, it converts your endpoints into curated toolsets, and deploys them as a hosted MCP server at &lt;code&gt;mcp.yourcompany.com&lt;/code&gt; in minutes. The key design insight is curation — rather than exposing all 200 endpoints as tools and overwhelming the agent's context, Gram lets you distill them down to 5–30 focused, purpose-built tools that represent complete workflows. For teams building in Python who want full control over the implementation, &lt;a href="https://github.com/jlowin/fastmcp" rel="noopener noreferrer"&gt;FastMCP&lt;/a&gt; is a high-level framework that strips out the protocol boilerplate and lets you define tools, resources, and prompts in straightforward Python.&lt;/p&gt;

&lt;p&gt;Most API companies aren't HubSpot. The right sequence for everyone else: get your OpenAPI spec right, write real descriptions, add &lt;code&gt;documentation_url&lt;/code&gt; to your errors, publish &lt;code&gt;/llms.txt&lt;/code&gt;, get indexed by Context7. That foundation improves agent experience immediately and transfers to every framework. Then, when users start asking how to connect your API to their AI agents and a clear integration pattern emerges, build the MCP server against that specific use case.&lt;/p&gt;

&lt;p&gt;Building MCP for the sake of having an MCP server is the equivalent of building a mobile app for the sake of having a mobile app. The agent experience of a half-built MCP server is worse than the agent experience of a good REST API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills: Teaching Agents to Do Your Job at 100x the Scale
&lt;/h2&gt;

&lt;p&gt;There's a mental model shift happening in engineering right now. AI agents aren't just writing code faster; they're becoming the engineers who run the product development loop. The new engineering job is building the agents that automate that loop for you, and then directing them to do it at a scale no individual could match.&lt;/p&gt;

&lt;p&gt;The simple version of this is already in use: Ghostty splits and tabs, tmux sessions, CLI agents in parallel. You run ten agents at once, each working a different branch or task. That's horizontal scalability for engineering work, something that was physically impossible before.&lt;/p&gt;

&lt;p&gt;But raw parallelism without direction is chaos. This is where skills come in.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are Skills?
&lt;/h3&gt;

&lt;p&gt;Skills are instruction packages for AI agents: Markdown files (optionally accompanied by scripts and resources) that teach an agent exactly how to handle a specific task. When the agent encounters work relevant to a skill, it loads that skill's context and operates with domain-specific precision instead of general-purpose guesswork.&lt;/p&gt;

&lt;p&gt;Anthropic introduced Skills as a first-class concept for Claude Code and the Claude ecosystem. Simon Willison &lt;a href="https://simonwillison.net/2025/Oct/16/claude-skills/" rel="noopener noreferrer"&gt;called them "maybe a bigger deal than MCP"&lt;/a&gt;, and his argument is compelling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Skills are conceptually extremely simple: a skill is a Markdown file telling the model how to do something, optionally accompanied by extra documents and pre-written scripts that the model can run to help it accomplish the tasks described by the skill.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key design insight is token efficiency. At session start, Claude reads only a brief YAML frontmatter description of each available skill, a few dozen tokens per skill. The full skill content only loads when the agent determines it's needed for the task at hand. You can have dozens of skills installed without burning your context window.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Agent Skills Ecosystem
&lt;/h3&gt;

&lt;p&gt;The ecosystem is moving fast. Vercel launched &lt;a href="https://github.com/vercel-labs/agent-skills" rel="noopener noreferrer"&gt;agent-skills&lt;/a&gt;, a directory and tooling layer for discovering, installing, and composing skills across coding agents. The skills.sh platform (used by Cursor, Claude Code, and others) makes the install experience as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add &amp;lt;package&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahrefs &lt;a href="https://github.com/ahrefs/ahrefs-api-skills" rel="noopener noreferrer"&gt;published their own skills&lt;/a&gt; to teach agents how to work with their SEO API, a direct example of how companies are now shipping agent instructions alongside their APIs. We took the same approach at Apideck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apideck API Skills
&lt;/h3&gt;

&lt;p&gt;We just launched &lt;a href="https://developers.apideck.com/building-with-llms" rel="noopener noreferrer"&gt;Apideck API Skills&lt;/a&gt;, installable via the skills.sh directory, to teach AI agents how to build integrations against our Unified API correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add apideck-libraries/api-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skills cover SDK usage across TypeScript, Python, Go, Java, PHP, and .NET; authentication and Vault patterns; pagination, error handling, and connector coverage checks; and migration paths from direct integrations to the unified layer.&lt;/p&gt;

&lt;p&gt;Instead of an agent making reasonable guesses about how our API works, it loads the relevant skill and operates with the same knowledge a senior Apideck engineer would bring to the task.&lt;/p&gt;

&lt;p&gt;This follows the same "building with LLMs" philosophy Stripe pioneered. They offer &lt;a href="https://docs.stripe.com/building-with-llms" rel="noopener noreferrer"&gt;plain-text docs, MCP server access, and agent toolkits&lt;/a&gt;, but skills go a layer deeper. Where plain-text docs make your documentation parseable, skills make your API learnable in the agent's execution context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills vs. MCP: Complementary, Not Competing
&lt;/h3&gt;

&lt;p&gt;It's worth being precise about where skills fit relative to MCP. MCPs give agents tools to call: actions they can take against live systems. Skills give agents the knowledge to use those tools well, or to accomplish tasks using the code execution environment directly.&lt;/p&gt;

&lt;p&gt;Simon Willison puts it well: almost everything you might achieve with an MCP can be handled by a CLI tool instead, because LLMs already know how to call &lt;code&gt;cli-tool --help&lt;/code&gt;. Skills have the same advantage, and you don't even need a CLI implementation. You drop in a Markdown file and let the model figure out execution.&lt;/p&gt;

&lt;p&gt;The two patterns compose naturally. An agent with an MCP connection to your accounting API and a skill that explains your data model and common patterns will outperform one that has only the MCP. Skills are the institutional knowledge layer; MCP is the capability layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Parallelism to Leverage
&lt;/h3&gt;

&lt;p&gt;Skills connect to the larger picture of agent orchestration. When you're running agents in parallel, on PRs, when an incident fires, when a customer files a bug, the quality ceiling is determined by how well each agent understands the domain it's working in. Skills are how you encode that domain knowledge once and distribute it across every agent instance, at any scale.&lt;/p&gt;

&lt;p&gt;The automation of the full product development loop is now an engineering responsibility. Skills are how you ensure that when your agents run that loop, at 100x the scale, while you sleep, they're running it the way you would have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI Rebirth
&lt;/h2&gt;

&lt;p&gt;There's a pattern Karpathy &lt;a href="https://x.com/karpathy/status/2026360908398862478" rel="noopener noreferrer"&gt;pointed to on February 24&lt;/a&gt; that cuts to something fundamental about agent-native interfaces: CLIs, the oldest developer tool, are having a second moment — not despite AI agents, but because of them.&lt;/p&gt;

&lt;p&gt;The reason is structural. Agents are terminal-native. They know how to run &lt;code&gt;--help&lt;/code&gt;, install packages via &lt;code&gt;pip&lt;/code&gt; or &lt;code&gt;npm&lt;/code&gt;, chain tools with pipes, and parse output. They don't need a bespoke integration layer to use a CLI. They just use it.&lt;/p&gt;

&lt;p&gt;Karpathy demonstrated this concretely: Claude installed the new Polymarket CLI, built a terminal dashboard showing the highest-volume prediction markets and their 24-hour price changes, and had it running in about three minutes. Pair that with the GitHub CLI and an agent can navigate repositories, review PRs, and act on real-world data signals in a single autonomous pipeline — no custom integration layer, no MCP server, nothing to maintain.&lt;/p&gt;

&lt;p&gt;This is the same point Willison makes about skills: almost everything you might achieve with an MCP server can be handled by a CLI, because agents already know how to use them. A well-designed CLI with good &lt;code&gt;--help&lt;/code&gt; output is self-documenting in a way that a REST API is not. An agent encountering &lt;code&gt;gh --help&lt;/code&gt; for the first time figures out the relevant subcommand on its own. An agent encountering your undescribed REST API hits a wall.&lt;/p&gt;

&lt;p&gt;The opportunity for API companies is direct: if you don't have a CLI, it's worth asking whether building one would be more leveraged than building an MCP server. A CLI that agents can install and explore immediately compounds differently. Skills make this even more powerful — a developer who loads your skill and has your CLI installed gives an agent both the domain knowledge and the execution surface at the same time.&lt;/p&gt;

&lt;p&gt;A few things make a CLI specifically better for agents: a &lt;code&gt;--json&lt;/code&gt; flag or JSON-by-default output so agents don't have to parse human-readable strings; composable subcommands following the &lt;code&gt;tool noun verb&lt;/code&gt; convention (like &lt;code&gt;gh repo list&lt;/code&gt;, &lt;code&gt;gh pr view&lt;/code&gt;) so &lt;code&gt;--help&lt;/code&gt; output is scannable; environment-variable-based auth so agents can configure credentials without interactive prompts; and error messages that explain what went wrong rather than just returning an exit code.&lt;/p&gt;

&lt;p&gt;Karpathy's framing for businesses is the right frame for API companies too: make your product agent-usable via markdown docs, skills, CLI, and MCP — roughly in that order of ease and durability. Each layer compounds on the last. Building the CLI doesn't require coordination with any AI provider, and it works immediately because agents can already use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underlying shift
&lt;/h2&gt;

&lt;p&gt;DX was about removing friction for humans. AX is the same discipline applied to autonomous consumers that can't ask for clarification and can't adapt when your API sends them somewhere unexpected.&lt;/p&gt;

&lt;p&gt;The things that made APIs good for developers — specificity, consistent semantics, helpful errors — matter even more when there's no human in the loop to compensate for ambiguity. Ambiguity that a developer resolves through experience becomes a failure mode at scale when agents are writing the integrations.&lt;/p&gt;

&lt;p&gt;The good news: most of what you'd do to make your API understandable to a machine is the same thing you'd do for a developer who doesn't already know your system. Start there.&lt;/p&gt;

&lt;p&gt;We're building &lt;a href="https://www.apideck.com" rel="noopener noreferrer"&gt;Apideck&lt;/a&gt;, a unified API for accounting integrations. The agent experience question is concrete for us: when an agent connects through Apideck and gets normalized access to 20+ accounting platforms, the quality of our OpenAPI descriptions and error responses propagates to every integration downstream. It focuses the mind.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>agents</category>
    </item>
    <item>
      <title>A Guide to Integrating with the NetSuite REST API</title>
      <dc:creator>𝚂𝚊𝚞𝚛𝚊𝚋𝚑 𝚁𝚊𝚒</dc:creator>
      <pubDate>Fri, 31 Oct 2025 14:28:04 +0000</pubDate>
      <link>https://forem.com/apideck/a-guide-to-integrating-with-the-netsuite-rest-api-1fch</link>
      <guid>https://forem.com/apideck/a-guide-to-integrating-with-the-netsuite-rest-api-1fch</guid>
      <description>&lt;h1&gt;
  
  
  A Guide to Integrating with the NetSuite REST API
&lt;/h1&gt;

&lt;p&gt;Accounting systems like &lt;a href="https://www.netsuite.com/portal/home.shtml" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; are the backbone for managing and optimizing business operations through automated processes and integrated workflows. NetSuite is a cloud-based enterprise resource planning (ERP) system that provides a business software suite for financial management, customer relationship management, e-commerce, and more.&lt;/p&gt;

&lt;p&gt;The NetSuite REST API allows developers to build modern integrations that connect NetSuite to other systems, automate complex workflows, and synchronize data across different platforms. Using this API, developers can automate tasks such as customer creation, order processing, and real-time inventory updates. This is key to building automated solutions that reduce manual effort and increase accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the NetSuite REST API Matters
&lt;/h2&gt;

&lt;p&gt;The NetSuite REST API provides a modern, JSON-based interface to NetSuite's core functionality, allowing you to interact with the platform programmatically using standard HTTP methods. You can use it to perform operations such as creating and updating records, managing customer data, processing transactions, and retrieving financial reports. The REST API enables you to integrate NetSuite into your existing systems and automate business-critical processes while ensuring data consistency across all applications.&lt;/p&gt;

&lt;p&gt;For example, you can automate workflows like customer onboarding or sync order data between NetSuite and external platforms, reducing manual errors and increasing operational efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  NetSuite SOAP API Alternative
&lt;/h2&gt;

&lt;p&gt;If you're evaluating NetSuite integration options, you might also consider the NetSuite SOAP API. While SOAP is more established and offers broader functionality coverage, it's significantly more complex to implement due to XML handling and verbose request structures. For a detailed comparison and implementation guide, see our &lt;a href="https://www.apideck.com/blog/guide-to-integrating-with-the-netsuite-soap-api" rel="noopener noreferrer"&gt;comprehensive NetSuite SOAP API integration guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are some examples of how you can use the REST API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automating customer management&lt;/strong&gt;: The NetSuite REST API can automatically create customer records when new users sign up on your website, sync contact information, and update customer data across systems. This eliminates duplicate data entry and ensures customer information stays current across all touchpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time inventory synchronization&lt;/strong&gt;: The REST API can integrate with e-commerce platforms like Shopify or WooCommerce to provide real-time inventory updates. When a product is sold online, the API immediately updates stock levels in NetSuite and can trigger reorder notifications when inventory falls below threshold levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated financial reporting&lt;/strong&gt;: The REST API can integrate with business intelligence tools to generate real-time financial dashboards. This automation provides up-to-date profit and loss statements, cash flow reports, and sales analytics, helping businesses make data-driven decisions quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Integrate with the NetSuite REST API
&lt;/h2&gt;

&lt;p&gt;NetSuite's REST API supports &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N3445710.html#bridgehead_4489663579" rel="noopener noreferrer"&gt;Token-Based Authentication (TBA)&lt;/a&gt; and &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1544634936.html" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; for secure API access.&lt;/p&gt;

&lt;p&gt;This guide focuses on Token-Based Authentication, which involves creating a signature using your NetSuite account credentials and including it with other authentication parameters in the request headers.&lt;/p&gt;

&lt;p&gt;To interact with the REST API, you need to:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/5VzgNoI6uxKtiBoJqAuyOk/e92fc303e42cdcddf34296d10281b04c/Screenshot_2025-08-28_at_03.16.32_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/5VzgNoI6uxKtiBoJqAuyOk/e92fc303e42cdcddf34296d10281b04c/Screenshot_2025-08-28_at_03.16.32_2x.png" alt="Screenshot 2025-08-28 at 03.16.32@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4248124361.html#bridgehead_4249074259" rel="noopener noreferrer"&gt;Create a user role&lt;/a&gt; with appropriate permissions, including REST Web Services access&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4249032125.html#procedure_4253065190" rel="noopener noreferrer"&gt;Create a new integration record&lt;/a&gt; for token-based authentication and obtain the consumer key and secret&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4254081947.html#procedure_4253065595" rel="noopener noreferrer"&gt;Create a new access token&lt;/a&gt; and obtain the token ID and secret&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/NApkpyess5TjyfhNOuAYO/be35e4635f5704f8020692313e53af6d/Screenshot_2025-08-28_at_03.17.39_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/NApkpyess5TjyfhNOuAYO/be35e4635f5704f8020692313e53af6d/Screenshot_2025-08-28_at_03.17.39_2x.png" alt="Screenshot 2025-08-28 at 03.17.39@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More detailed information is available in the &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1540391670.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplify Authentication with Apideck Vault
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/2f6lAi0UUMn2nNoQQNgPnO/455c35557f23d07fbf366049800c346f/Screenshot_2025-08-28_at_03.19.50_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/2f6lAi0UUMn2nNoQQNgPnO/455c35557f23d07fbf366049800c346f/Screenshot_2025-08-28_at_03.19.50_2x.png" alt="Screenshot 2025-08-28 at 03.19.50@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The authentication setup process above can be complex and time-consuming for your end users. &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;Apideck Vault&lt;/a&gt; provides a white-label, hosted authentication interface that eliminates this complexity entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/4Ye1Qgt1wUoxmrx6k6lhIo/0a550a65640b33e30676c6fc1cbfaaa7/Screenshot_2025-08-28_at_03.18.42_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/4Ye1Qgt1wUoxmrx6k6lhIo/0a550a65640b33e30676c6fc1cbfaaa7/Screenshot_2025-08-28_at_03.18.42_2x.png" alt="Screenshot 2025-08-28 at 03.18.42@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Vault, your users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Connect NetSuite in seconds&lt;/strong&gt; - No technical setup required on their end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure credential storage&lt;/strong&gt; - OAuth tokens and API keys are stored safely with enterprise-grade security
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified experience&lt;/strong&gt; - Same interface works for NetSuite, QuickBooks, Xero, and 200+ other integrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-service management&lt;/strong&gt; - Users can connect, disconnect, and manage integrations independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking users to create integration records and access tokens, they simply authenticate through Vault's hosted interface. You get the integration data you need without the setup friction that kills conversion rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a Request to the NetSuite REST API
&lt;/h2&gt;

&lt;p&gt;The base URL for NetSuite REST API requests follows this format:&lt;br&gt;
&lt;code&gt;https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/{record_type}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;NetSuite uses OAuth 1.0a for authentication, which requires generating a signature for each request. Here's a Python example:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OAuth1&lt;/span&gt;

&lt;span class="c1"&gt;# NetSuite credentials
&lt;/span&gt;&lt;span class="n"&gt;account_id&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_account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;consumer_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_consumer_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 
&lt;span class="n"&gt;consumer_secret&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_consumer_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;token_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_token_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;token_secret&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_token_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Create OAuth1 auth object
&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OAuth1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;consumer_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resource_owner_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resource_owner_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signature_method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HMAC-SHA256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signature_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AUTH_HEADER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example: Get a customer record
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.suitetalk.api.netsuite.com/services/rest/record/v1/customer/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="si"&gt;}&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;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="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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&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="k"&gt;return&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="c1"&gt;# Example: Create a customer record
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.suitetalk.api.netsuite.com/services/rest/record/v1/customer&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;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="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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&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="n"&gt;customer_data&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;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="c1"&gt;# Usage examples
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Get customer with ID 123
&lt;/span&gt;    &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer data:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create a new customer
&lt;/span&gt;    &lt;span class="n"&gt;new_customer&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;companyName&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;Acme Corporation&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;contact@acme.com&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;+1-555-0123&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;create_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_customer&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Created customer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error making API request:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Integration Challenge
&lt;/h2&gt;

&lt;p&gt;As you can see from the examples above, integrating directly with the NetSuite REST API involves several challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authentication complexity&lt;/strong&gt;: Implementing OAuth 1.0a signature generation requires precise handling of encoding, sorting, and hashing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting management&lt;/strong&gt;: NetSuite has &lt;a href="https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1557159142.html" rel="noopener noreferrer"&gt;strict concurrency limits&lt;/a&gt; that require careful request throttling to avoid timeouts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom field handling&lt;/strong&gt;: NetSuite's custom fields require specific formatting and field ID mapping that varies by account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling complexity&lt;/strong&gt;: NetSuite's error responses require specific parsing and retry logic for different error types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data transformation&lt;/strong&gt;: NetSuite's data structures often don't match your application's data models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pagination complexity&lt;/strong&gt;: Handling large datasets requires implementing cursor-based pagination logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook limitations&lt;/strong&gt;: NetSuite's webhook support is limited, requiring polling for real-time data needs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these challenges adds development time and increases the potential for bugs in your integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Apideck Unified Accounting API
&lt;/h2&gt;

&lt;p&gt;Integrating with multiple accounting systems, including NetSuite, can be overwhelming and time-consuming. Managing OAuth signatures, handling rate limits, and dealing with different data formats across various platforms requires months of development effort and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck Unified Accounting API&lt;/a&gt; provides a single integration point for 20+ accounting platforms, including NetSuite. This approach abstracts away the complexities of individual APIs, simplifying the integration process.&lt;/p&gt;

&lt;p&gt;Key benefits of using the Apideck Unified Accounting API include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time data processing&lt;/strong&gt;: All API calls are processed in real-time, not batched, ensuring your data is always fresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed authentication with Vault&lt;/strong&gt;: &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;Apideck Vault&lt;/a&gt; handles all OAuth flows and credential management, eliminating complex authentication setup for your users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified data model&lt;/strong&gt;: Consistent data structures across all 18+ accounting platforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in rate limit management&lt;/strong&gt;: Automatic throttling and retry logic with exponential backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook emulation&lt;/strong&gt;: Get push notifications from platforms that support webhooks, with Apideck emulating webhooks for platforms that don't&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;White-label user interface&lt;/strong&gt;: Easily embedded interface that gives users a simple and secure connection experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data normalization&lt;/strong&gt;: Messy, inconsistent APIs are normalized into a single structure while still exposing raw downstream information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced maintenance burden&lt;/strong&gt;: Updates to underlying accounting systems don't require changes to your integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Making Requests with Apideck
&lt;/h2&gt;

&lt;p&gt;Before making API requests through Apideck, you need to &lt;a href="https://developers.apideck.com/connectors/netsuite/docs/consumer+connection" rel="noopener noreferrer"&gt;configure your NetSuite connection&lt;/a&gt; in the Apideck platform.&lt;/p&gt;

&lt;p&gt;Here's how simple the same operations become with Apideck's unified API. You can try these requests in our Api Explorer, which makes this even simpler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&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;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-apideck-app-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="s2"&gt;&amp;lt;YOUR-APP-ID&amp;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="s2"&gt;Authorization&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="s2"&gt;Bearer &amp;lt;YOUR-API-KEY&amp;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="s2"&gt;x-apideck-consumer-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="s2"&gt;&amp;lt;CONSUMER-ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Your NetSuite consumer&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="s2"&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="c1"&gt;// Get a customer - unified across all accounting platforms&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="nx"&gt;axios&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="s2"&gt;`https://unify.apideck.com/accounting/customers/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customerId&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="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&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;response&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error retrieving customer:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;// Create a customer - same API call works for NetSuite, QuickBooks, Xero, etc.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="nx"&gt;axios&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unify.apideck.com/accounting/customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&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;response&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error creating customer:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;// List all invoices with automatic pagination&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getInvoices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="nx"&gt;axios&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unify.apideck.com/accounting/invoices&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;headers&lt;/span&gt; &lt;span class="p"&gt;}&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;response&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error retrieving invoices:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;// Usage examples&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&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;getCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Customer:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customer&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;newCustomer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Acme Corporation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact@acme.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+1-555-0123&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;createdCustomer&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;createCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCustomer&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Created customer:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdCustomer&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getInvoices&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoices:&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Here’s an example of the same request from Apideck’s &lt;a href="https://developers.apideck.com/api-explorer" rel="noopener noreferrer"&gt;API Explorer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can test and make API calls with the simple drag and drop UI, which can easily pre-fill the response headers and body with the required data. You can see the &lt;code&gt;list customers&lt;/code&gt; request being done in a single shot.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/3Ab7l6AtTlpDsnWpkSLR8g/1ed51da4e0a1067e83e945be628dbdc5/Screenshot_2025-08-28_at_03.27.39_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3Ab7l6AtTlpDsnWpkSLR8g/1ed51da4e0a1067e83e945be628dbdc5/Screenshot_2025-08-28_at_03.27.39_2x.png" alt="Screenshot 2025-08-28 at 03.27.39@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compare this clean, simple code to the complex OAuth signature generation and authentication handling required for direct NetSuite integration. With Apideck, the same code works across NetSuite, QuickBooks, Xero, Sage, and dozens of other accounting platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Integration Examples
&lt;/h2&gt;

&lt;p&gt;Here are some practical examples showing how Apideck simplifies common NetSuite integration scenarios. Please note, you can try them out from our Api Explorer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E-commerce Order Sync:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sync new orders from your e-commerce platform to NetSuite&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncOrderToNetSuite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customer&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="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_id&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="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;item&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="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;unit_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;item&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unify.apideck.com/accounting/invoices&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&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;&lt;strong&gt;Customer Data Synchronization:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Keep customer data in sync between your CRM and NetSuite&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncCustomerData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;billing_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;line1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;postal_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_zip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crmCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_country&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unify.apideck.com/accounting/customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this guide, you learned how to integrate with the NetSuite REST API and discovered the complexities involved in direct integration. As demonstrated, working directly with NetSuite's API requires managing complex OAuth 1.0a signatures, handling rate limits, dealing with custom field mappings, and maintaining error-prone authentication code.&lt;/p&gt;

&lt;p&gt;These challenges multiply when you need to support multiple accounting platforms beyond NetSuite. Each system has its own authentication methods, data structures, and API quirks that require separate implementations and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;To simplify your accounting integrations and eliminate the complexities of direct API integration, consider using the &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck Unified Accounting API&lt;/a&gt; with the &lt;a href="https://www.apideck.com/connectors/netsuite" rel="noopener noreferrer"&gt;NetSuite Connector&lt;/a&gt;. By providing a single, consistent interface across all major accounting platforms, Apideck simplifies development, reduces maintenance overhead, and saves valuable development time and resources.&lt;/p&gt;

&lt;p&gt;With Apideck, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to NetSuite and 20+ other accounting platforms with the same code&lt;/li&gt;
&lt;li&gt;Skip complex authentication implementations
&lt;/li&gt;
&lt;li&gt;Get standardized data models across all platforms&lt;/li&gt;
&lt;li&gt;Benefit from built-in rate limiting and error handling&lt;/li&gt;
&lt;li&gt;Access real-time data processing (not batch processing)&lt;/li&gt;
&lt;li&gt;Use webhook emulation for platforms that don't natively support webhooks&lt;/li&gt;
&lt;li&gt;Focus on building features instead of managing integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://platform.apideck.com/api/auth/login?product=unify&amp;amp;screenHint=signup&amp;amp;connector=netsuite" rel="noopener noreferrer"&gt;Sign up for Apideck&lt;/a&gt; today to start simplifying your NetSuite and accounting integrations.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What's the difference between NetSuite REST API and SOAP API?
&lt;/h3&gt;

&lt;p&gt;The REST API is newer (2019), uses JSON, and is easier to implement, but has limited functionality coverage. The SOAP API has been around longer, offers broader NetSuite feature access, but requires XML handling and is more complex to implement. For bulk operations and advanced features, SOAP is often better. For modern web/mobile apps and simple CRUD operations, REST is preferred. See our &lt;a href="https://www.apideck.com/blog/guide-to-integrating-with-the-netsuite-soap-api" rel="noopener noreferrer"&gt;SOAP API guide&lt;/a&gt; for a detailed comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle NetSuite's rate limits?
&lt;/h3&gt;

&lt;p&gt;NetSuite uses concurrency-based rate limiting rather than traditional rate limits. Each account has a limit on concurrent API requests (typically 10-25 concurrent requests). When you exceed this limit, you'll get timeout errors rather than 429 rate limit errors. Implement exponential backoff retry logic and consider queuing requests during high-traffic periods. Monitor your usage through NetSuite's API monitoring tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use OAuth 2.0 with NetSuite REST API?
&lt;/h3&gt;

&lt;p&gt;Yes, NetSuite supports OAuth 2.0 for REST API authentication, but Token-Based Authentication (TBA) is more commonly used because tokens don't expire like OAuth 2.0 access tokens do. OAuth 2.0 requires refresh token management and has a 7-day refresh limit, making TBA more suitable for server-to-server integrations. However, if you need user-facing authentication flows, OAuth 2.0 is the better choice.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>api</category>
      <category>tutorial</category>
      <category>backend</category>
    </item>
    <item>
      <title>DATEV API Integration: A Comprehensive Technical Guide</title>
      <dc:creator>𝚂𝚊𝚞𝚛𝚊𝚋𝚑 𝚁𝚊𝚒</dc:creator>
      <pubDate>Fri, 31 Oct 2025 14:27:33 +0000</pubDate>
      <link>https://forem.com/apideck/datev-api-integration-a-comprehensive-technical-guide-24l6</link>
      <guid>https://forem.com/apideck/datev-api-integration-a-comprehensive-technical-guide-24l6</guid>
      <description>&lt;p&gt;When German businesses need accounting software, they turn to DATEV. With decades of market dominance and adoption by thousands of tax consultants across Germany, DATEV has become the de facto standard for financial record-keeping in the German market. But here's where things get interesting for developers: integrating with DATEV isn't like connecting to your typical REST API. The architecture is fundamentally different, and understanding these differences is critical to building a successful integration.&lt;/p&gt;

&lt;p&gt;If you're building an ERP system, e-commerce platform, or any business application that needs to sync financial data with German accounting workflows, you'll need to understand how DATEV integration actually works. This guide will walk you through the technical architecture, explain why DATEV takes a batch-processing approach instead of real-time REST calls, and show you the practical steps for implementing a robust integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding DATEV API Integration
&lt;/h2&gt;

&lt;p&gt;DATEV API integration connects your third-party platform to DATEV's accounting software ecosystem, enabling automated synchronization of financial data without manual file imports or error-prone copy-paste workflows. When you integrate with DATEV, you're connecting to the systems used by tax consultants and accountants across Germany to manage client finances, prepare tax filings, and maintain compliance with German accounting regulations.&lt;/p&gt;

&lt;p&gt;The typical use cases span multiple business domains. E-commerce platforms sync order data and payment records. Banking systems push transaction updates for reconciliation. Property management software sends rent payments and tenant invoicing data. Payroll systems transfer employee expense information. In each case, the goal is the same: eliminate manual data entry, reduce errors, and enable real-time visibility into financial operations.&lt;/p&gt;

&lt;p&gt;What makes DATEV integration valuable is the automation of repetitive accounting tasks. Instead of your accountant manually importing CSV files and checking for discrepancies, your integration automatically creates properly formatted records that flow directly into the accounting workflow. Invoice data, customer and supplier records, journal entries, cost center allocations, and VAT calculations all sync automatically. The result is enhanced compliance with German accounting regulations, reduced data errors, improved accuracy, and proactive financial management based on current data rather than week-old exports.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Types of DATEV APIs
&lt;/h2&gt;

&lt;p&gt;Here's where DATEV diverges sharply from typical API architectures. DATEV offers two primary data exchange formats, and neither one works like the REST or SOAP APIs you're probably familiar with. Understanding this distinction is absolutely critical before you start building your integration.&lt;/p&gt;

&lt;p&gt;The CSV-based API, used for DATEV Rechnungswesen (abbreviated as ReWe), handles finalized financial bookings and accounting records. When you need to submit completed transactions that are ready for the permanent accounting record, you use this approach. Data flows through flat, tabular CSV files that must follow precise column formatting requirements. These files are processed through batch jobs called EXTF-Jobs. This approach is common in on-premise environments and legacy integrations where the infrastructure was built before cloud-native architectures became standard.&lt;/p&gt;

&lt;p&gt;The XML-based API, used for DATEV Unternehmen Online (abbreviated as DUo), handles booking suggestions and invoice processing. This format supports complex, hierarchical data structures that represent more nuanced financial information. Data moves through structured XML files that are validated against XSD schemas to ensure compliance. These files are processed with batch jobs called dxso-Jobs. The XML approach focuses on cloud platforms and modern workflows, particularly for scenarios where you're proposing bookings that require accountant review before finalization.&lt;/p&gt;

&lt;p&gt;Now here's the critical point that trips up many developers: DATEV does &lt;strong&gt;not&lt;/strong&gt; use traditional REST or SOAP APIs for core accounting operations. If you're expecting to make HTTP POST requests with JSON payloads and get immediate synchronous responses, you're approaching this wrong. DATEV's integration relies on asynchronous batch-based file processing. You submit files as jobs, those jobs enter a processing queue, and DATEV systems handle them on their own schedule. You cannot use typical RESTful calls for submitting bookings or financial records.&lt;/p&gt;

&lt;p&gt;There are some exceptions worth noting. DATEV does offer REST endpoints for certain niche products. The Cash Register Import API (MeinFiskal) provides documented REST endpoints for importing electronic cash register data. The Document Management system integrates via REST APIs through DATEVconnect for document workflows. But these are specialized use cases, not the core accounting functionality.&lt;/p&gt;

&lt;p&gt;Third-party unified API providers sometimes expose REST endpoints that make DATEV integration feel more familiar to developers. But understand what's happening under the hood: these are wrappers that translate your REST calls into DATEV's batch processing behind the scenes. The underlying architecture remains batch-based, even if the developer experience feels more REST-like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with the accounting:documents API
&lt;/h2&gt;

&lt;p&gt;Let me show you a practical example using the accounting:documents REST API, which handles document uploads to Belege online in DATEV Unternehmen online. This is one of the REST endpoints that DATEV provides, and it demonstrates the authentication and data handling patterns you'll encounter.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/3YCIyGW6jYowcSsVJpq6qe/3362763c323a3589ba4261711e9fcdc5/Screenshot_2025-10-09_at_02.43.54_2x.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3YCIyGW6jYowcSsVJpq6qe/3362763c323a3589ba4261711e9fcdc5/Screenshot_2025-10-09_at_02.43.54_2x.png" alt="Screenshot 2025-10-09 at 02.43.54@2x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, you need to retrieve the list of clients your authenticated user can access. This establishes which companies you have permission to integrate with:&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 https://accounting-documents.api.datev.de/platform/v2/clients
Authorization: Bearer {access_token}
X-DATEV-Client-Id: {your_client_id}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you client identifiers that you'll use in subsequent requests:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_number"&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;"consultant_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;455148&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;"455148-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;"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;"Muster GmbH 1"&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;That &lt;code&gt;id&lt;/code&gt; field is the technical identifier you'll use for all data operations. Notice that it combines the consultant number and client number, creating a unique reference for this specific client within the DATEV system.&lt;/p&gt;

&lt;p&gt;Before uploading documents, you should check what document types are available for this client. Document types determine how DATEV processes and categorizes your uploads:&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 https://accounting-documents.api.datev.de/platform/v2/clients/455148-1/document-types
Authorization: Bearer {access_token}
X-DATEV-Client-Id: {your_client_id}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the configured document types:&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="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;"Rechnungseingang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoices_received"&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_credit_identifier"&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="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;"Rechnungsausgang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"outgoing_invoices"&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_credit_identifier"&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="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;"Kasse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"other_documents"&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;Now you can upload a document. This uses multipart form data to combine the file with metadata:&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://accounting-documents.api.datev.de/platform/v2/clients/455148-1/documents
Authorization: Bearer {access_token}
X-DATEV-Client-Id: {your_client_id}
Content-Type: multipart/form-data

--boundary
Content-Disposition: form-data; name="file"; filename="invoice-2024-001.pdf"
Content-Type: application/pdf

[Binary PDF content]
--boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{
  "document_type": "Rechnungseingang",
  "note": "Supplier invoice for office supplies",
  "category": "MyERP",
  "folder": "Invoices",
  "register": "2024/10"
}
--boundary--
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metadata structure organizes documents within DATEV's document repository using a three-level hierarchy: category, folder, and register. If you specify any of these levels, you must provide all three. This organizational structure helps accountants locate documents efficiently within the DATEV interface.&lt;/p&gt;

&lt;p&gt;When the upload succeeds, you receive confirmation with file 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;"file"&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;"invoice-2024-001.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;125742&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"upload_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-10-09T14:23:12+02: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;"media_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;"application/pdf"&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;"document_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;"Rechnungseingang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Supplier invoice for office supplies"&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;
  
  
  Authentication and Authorization
&lt;/h2&gt;

&lt;p&gt;DATEV uses OAuth2 for authorization, particularly for cloud-based integrations with Unternehmen Online. You need to design secure user consent workflows that clearly explain what data your application will access and how it will be used. Many integration scenarios require third-party approval or validation from the tax consultant who manages the client's account, since tax consultants control client access within the DATEV ecosystem.&lt;/p&gt;

&lt;p&gt;Your OAuth implementation should support long-lived tokens with automatic renewal processes. This is critical because DATEV integrations often need continuous data synchronization without requiring users to re-authenticate frequently. Users expect their accounting data to flow automatically once they've granted initial consent.&lt;/p&gt;

&lt;p&gt;The typical OAuth flow follows this pattern: redirect the user to DATEV's authorization endpoint, receive an authorization code after the user grants consent, exchange that code for access and refresh tokens, and use the access token for API requests. When the access token expires, use the refresh token to obtain a new access token without requiring user interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Format Compliance and Validation
&lt;/h2&gt;

&lt;p&gt;DATEV enforces strict format requirements because accounting data must maintain audit compliance and regulatory standards. The CSV and XML formats are fixed specifications that vary by product. You cannot choose arbitrary formats or invent your own column structures.&lt;/p&gt;

&lt;p&gt;For XML submissions to Unternehmen Online, your files must validate against DATEV's XSD schemas before processing will succeed. The schemas define required fields, data types, value constraints, and structural relationships. XML validation failures result in immediate job rejection with error codes that indicate which validation rules you violated.&lt;/p&gt;

&lt;p&gt;CSV files for Rechnungswesen demand precise column formatting. Each column must contain the correct data type in the expected position. Column order matters. Field delimiters must match the specification exactly. Even minor deviations cause processing failures because the DATEV import engine cannot risk misinterpreting financial data.&lt;/p&gt;

&lt;p&gt;Beyond basic format validation, you must properly structure metadata that accompanies your financial records. Cost center allocations, voucher numbers, analytic account references, and VAT information all need to follow DATEV's coding schemes. German VAT codes, for instance, follow specific conventions that DATEV recognizes and validates against current tax regulations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Error Scenarios
&lt;/h2&gt;

&lt;p&gt;The OpenAPI specification documents numerous error codes you'll encounter. Let me walk through the most common ones and what they mean for your integration.&lt;/p&gt;

&lt;p&gt;Error &lt;code&gt;#DCO01010 restapi.InvalidDocumentTypeFault&lt;/code&gt; occurs when you specify a document type that doesn't exist for the client. Remember that each client configures their own document types. You must query the available types first and only use values from that list. Don't assume standard document types exist universally.&lt;/p&gt;

&lt;p&gt;Error &lt;code&gt;#DCO01015 restapi.UnsupportedFileTypeFault&lt;/code&gt; indicates the file format isn't accepted. DATEV restricts uploads to specific file types for security and compatibility reasons. The allowed formats vary between DuoNext and legacy systems. Always check the permitted file extensions for your target environment.&lt;/p&gt;

&lt;p&gt;Error &lt;code&gt;#DCO01016 restapi.FileSizeFault&lt;/code&gt; means your file exceeds the twenty-megabyte limit. Large files create processing and storage burdens. If you're handling oversized documents, you'll need to compress them, split them into multiple uploads, or reconsider your document workflow. DATEV recommends warning users when files exceed 500 KB because even allowed file sizes can slow down system performance.&lt;/p&gt;

&lt;p&gt;Error &lt;code&gt;#DCO01253 restapi.DuplicateFileFault&lt;/code&gt; signals that a file with identical name and document type already exists in Belege Online. DATEV prevents duplicate uploads to avoid confusion. Your integration should either generate unique filenames, allow users to specify different document types for duplicates, or implement deduplication logic before attempting uploads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration Approaches: Direct vs. Unified APIs
&lt;/h2&gt;

&lt;p&gt;You face a fundamental architectural decision: integrate directly with DATEV or use a unified API provider that abstracts the complexity. Each approach has distinct tradeoffs that affect your development timeline, maintenance burden, and feature coverage.&lt;/p&gt;

&lt;p&gt;Direct integration with DATEV gives you complete control over the integration logic. You're not dependent on third-party service availability or pricing changes. You can access all DATEV features without waiting for a middleware provider to expose them. But this control comes with significant complexity. You need deep understanding of CSV and XML format specifications. You're responsible for all batch job management, status monitoring, error handling, and retry logic. Testing requires thorough validation across different client configurations and fiscal year setups. Development cycles stretch longer because you're implementing the entire integration stack yourself. Ongoing maintenance becomes a permanent operational burden as DATEV updates their APIs and format requirements.&lt;/p&gt;

&lt;p&gt;Unified API providers like Chift, Maesn, and others offer a RESTful developer experience that feels familiar to modern web developers. These platforms automatically handle batch job creation and management behind the scenes. They provide built-in error handling and retry logic that's been tested across many production deployments. Most importantly, they support multiple accounting platforms beyond DATEV, so your integration code can potentially connect to QuickBooks, Xero, NetSuite, and other systems using the same API patterns. Time to market drops dramatically because you're leveraging pre-built infrastructure. Maintenance overhead shifts to the unified API provider.&lt;/p&gt;

&lt;p&gt;The downsides of unified APIs include dependency on third-party service reliability and additional costs beyond DATEV's pricing. Not all DATEV features may be exposed through the unified API wrapper, potentially limiting your integration's capabilities. You're also adding network hops and processing delays compared to direct DATEV communication.&lt;/p&gt;

&lt;p&gt;For most development teams, especially those building multi-platform integrations or operating under tight deadlines, unified API providers offer the better path. The productivity gains and reduced maintenance burden usually outweigh the additional costs and potential feature limitations. Direct DATEV integration makes sense primarily for specialized use cases that require features not available through unified APIs, or for large-scale deployments where the volume justifies investing in custom infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Implementation Considerations
&lt;/h2&gt;

&lt;p&gt;When you start implementing your DATEV integration, several practical concerns will surface immediately. File naming conventions matter more than you might expect. DATEV recommends UTF-8 encoding with precomposed Unicode characters. Decomposed characters cause problems. Special characters like umlauts must be single code points, not multi-byte sequences. DATEV maintains a whitelist of permitted characters in filenames, and unsupported characters get replaced with underscores automatically. This can create confusion if your system generates filenames with characters outside the whitelist.&lt;/p&gt;

&lt;p&gt;Rate limiting and batch processing constraints affect how you design your data synchronization strategy. You cannot stream continuous updates in real-time. Instead, you batch operations and submit them periodically. Determine appropriate batch sizes that balance efficiency against error handling complexity. Larger batches process more efficiently but make error diagnosis harder when something fails.&lt;/p&gt;

&lt;p&gt;Error recovery needs thoughtful design because batch processing creates asynchronous failure modes. When a job fails, you need to detect the failure, parse error messages, determine which records caused problems, and decide whether to retry the entire batch or just the failed records. Your integration should implement exponential backoff for transient failures while permanently failing for validation errors that won't resolve through retries.&lt;/p&gt;

&lt;p&gt;Testing strategies require access to DATEV sandbox environments. The API specification shows separate endpoints for sandbox testing. Use these extensively before deploying to production. Test with various client configurations, different fiscal year setups, and edge cases in your data. Remember that DATEV may automatically generate subsequent fiscal years under certain conditions, so test your integration's behavior when fiscal year transitions occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Recommendations
&lt;/h2&gt;

&lt;p&gt;DATEV integration is fundamentally different from typical REST API integration work. The batch-processing architecture, strict format requirements, and certification processes create complexity that shouldn't be underestimated. Before starting development, evaluate your technical resources honestly. Do you have developers with experience in batch processing systems? Can you invest time in understanding DATEV's specific format requirements? Do you need features that unified API providers don't expose?&lt;/p&gt;

&lt;p&gt;For most teams, starting with a unified API provider offers the fastest path to a working integration. You can always migrate to direct DATEV integration later if specific requirements demand it. Focus initially on understanding the business workflows, data mapping requirements, and error handling strategies. These concerns remain constant regardless of whether you integrate directly or through middleware.&lt;/p&gt;

&lt;p&gt;Document your integration thoroughly, especially the format specifications and validation requirements. Future developers maintaining your integration will need this context. Build comprehensive error logging from day one because diagnosing batch processing failures requires detailed audit trails.&lt;/p&gt;

&lt;p&gt;DATEV integration enables powerful accounting automation, but success requires respecting the architectural constraints and compliance requirements that make DATEV the trusted standard for German accounting workflows.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>api</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
