<?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: Kate Apideck</title>
    <description>The latest articles on Forem by Kate Apideck (@kate_apideck).</description>
    <link>https://forem.com/kate_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%2Fuser%2Fprofile_image%2F2899028%2F004a9b1a-6552-459f-bc74-604f53f732cb.jpg</url>
      <title>Forem: Kate Apideck</title>
      <link>https://forem.com/kate_apideck</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kate_apideck"/>
    <language>en</language>
    <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>
  </channel>
</rss>
