<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kate Apideck</title>
    <description>The latest articles on DEV Community by Kate Apideck (@kate_apideck).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2899028%2F004a9b1a-6552-459f-bc74-604f53f732cb.jpg</url>
      <title>DEV Community: Kate Apideck</title>
      <link>https://dev.to/kate_apideck</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kate_apideck"/>
    <language>en</language>
    <item>
      <title>How to Integrate with the MYOB API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:13:02 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-myob-api-3gf5</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-myob-api-3gf5</guid>
      <description>&lt;p&gt;MYOB dominates the accounting software market in Australia and New Zealand. If your SaaS product serves businesses in the ANZ region, your customers are probably asking for MYOB integration. This guide covers authentication (including the changes MYOB introduced in March 2025), the ongoing deprecation of AccountRight Classic, and the practical details of working with the API.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;h3&gt;
  
  
  Admin-only authorization
&lt;/h3&gt;

&lt;p&gt;As of March 2025, only administrator users of a MYOB company file can approve OAuth requests. This is a security enhancement. If your users attempt to authorize with a non-admin account, the flow fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Company file GUID retrieval
&lt;/h3&gt;

&lt;p&gt;The old endpoint &lt;code&gt;GET https://api.myob.com/accountright/&lt;/code&gt; no longer returns company files for new API keys. Instead, MYOB returns the company file GUID (businessId) in the redirect URI after successful authorization.&lt;/p&gt;

&lt;p&gt;You must include &lt;code&gt;prompt=consent&lt;/code&gt; in your authorization URL to ensure the businessId is returned. The authorization URL looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://secure.myob.com/oauth2/account/authorize
  ?client_id=YOUR_CLIENT_ID
  &amp;amp;redirect_uri=https://yourapp.com/redirect
  &amp;amp;response_type=code
  &amp;amp;scope=sme-company-file sme-contact sme-sale
  &amp;amp;prompt=consent
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the user authorizes, MYOB redirects to your URI with both a code and businessId parameter. Use the code to exchange for access and refresh tokens. Store the businessId as the company file identifier for all subsequent API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token lifecycle
&lt;/h3&gt;

&lt;p&gt;Access tokens expire in approximately 20 minutes. When you receive tokens, MYOB includes an expiry timestamp. Implement token refresh before expiration to maintain uninterrupted access. The refresh token request uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://secure.myob.com/oauth2/v1/authorize
Content-Type: application/x-www-form-urlencoded

client_id=YOUR_CLIENT_ID
&amp;amp;client_secret=YOUR_SECRET
&amp;amp;refresh_token=REFRESH_TOKEN
&amp;amp;grant_type=refresh_token
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store tokens securely and never log them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key API endpoints
&lt;/h2&gt;

&lt;p&gt;The MYOB Business API organizes endpoints by business function. The base URL for cloud files is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://arl2.api.myob.com/accountright/{company_file_id}/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local desktop files, the API runs on &lt;code&gt;localhost:8080/accountright/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contacts
&lt;/h3&gt;

&lt;p&gt;Contacts cover customers, suppliers, and employees.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Customer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer records for invoicing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Supplier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Supplier records for bills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Employee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee records for payroll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Contact/Personal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Other contacts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Contacts require a &lt;code&gt;Name&lt;/code&gt; field at minimum. Each contact gets a unique UID that you use when creating invoices or bills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales
&lt;/h3&gt;

&lt;p&gt;Sales endpoints handle customer-facing transactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Invoice/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Product-based invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Invoice/Service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service-based invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/CustomerPayment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payments received from customers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/Quote/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quotes for products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Sale/CreditSettlement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credit note applications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When creating invoices, you POST to the specific invoice type endpoint. The generic &lt;code&gt;/Sale/Invoice&lt;/code&gt; endpoint only supports GET requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Purchases
&lt;/h3&gt;

&lt;p&gt;Purchase endpoints mirror the sales structure for supplier transactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Bill/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bills for purchased items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Bill/Service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service bills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/SupplierPayment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payments to suppliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Purchase/Order/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Purchase orders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  General Ledger
&lt;/h3&gt;

&lt;p&gt;Ledger endpoints provide access to the chart of accounts and financial data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/Account&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chart of accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/TaxCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tax rate definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/JournalTransaction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Posted journal entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/GeneralJournal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual journal entry creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/GeneralLedger/Category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tracking categories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Inventory
&lt;/h3&gt;

&lt;p&gt;Inventory endpoints manage products and stock.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products and services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Adjustment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stock adjustments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Inventory/Location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Warehouse locations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Request headers and versioning
&lt;/h2&gt;

&lt;p&gt;The API requires specific headers for all requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer ACCESS_TOKEN
x-myobapi-key: YOUR_CLIENT_ID
x-myobapi-version: v2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always specify &lt;code&gt;x-myobapi-version: v2&lt;/code&gt;. The default is v1, which lacks GUID support and causes problems. This is a common source of errors for new integrations.&lt;/p&gt;

&lt;p&gt;For cloud files, include the company file token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;x-myobapi-cftoken: BASE64_ENCODED(username:password)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cftoken contains the company file username and password, base64 encoded with a colon separator. For files authenticated via OAuth 2.0 with admin access, this may not be required, but older implementations and desktop files need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data mapping considerations
&lt;/h2&gt;

&lt;p&gt;When syncing data between your application and MYOB, keep these mapping issues in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoice structure
&lt;/h3&gt;

&lt;p&gt;MYOB supports five invoice types: Item, Service, Professional, TimeBilling, and Miscellaneous. Each has a different line item structure. Most integrations use Item or Service invoices.&lt;/p&gt;

&lt;p&gt;Line items require references to existing inventory items or tax codes. You cannot create an invoice with inline item definitions. Fetch or create the required items first, then reference them by UID in the invoice payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tax codes
&lt;/h3&gt;

&lt;p&gt;MYOB stores tax rates as TaxCode objects with UIDs. Common Australian tax codes include GST (10%), GST Free, and Input Taxed. Your application needs to map its tax logic to the appropriate MYOB tax codes, which you can retrieve from &lt;code&gt;/GeneralLedger/TaxCode&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account mapping
&lt;/h3&gt;

&lt;p&gt;Line items on invoices and bills link to ledger accounts. MYOB uses account numbers and UIDs. Retrieve the chart of accounts via &lt;code&gt;/GeneralLedger/Account&lt;/code&gt; to map your categories to the correct accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Currency
&lt;/h3&gt;

&lt;p&gt;MYOB supports multi-currency transactions. Currency codes follow ISO 4217. If your customers use foreign currencies, check whether their MYOB file has multi-currency enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling and rate limits
&lt;/h2&gt;

&lt;p&gt;The API returns standard HTTP status codes: 200 for success, 201 for created, 400 for bad request, 401 for authentication errors, and 404 for not found.&lt;/p&gt;

&lt;p&gt;Error responses include JSON with error codes and messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ValidationError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Customer UID is required."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate limiting
&lt;/h3&gt;

&lt;p&gt;The default limits are 8 requests per second and 1,000,000 requests per day per API key. When you hit a rate limit, the API returns a 403 status (not 429, which can be confusing). Implement exponential backoff and retry logic.&lt;/p&gt;

&lt;p&gt;The 29-second timeout limit means long-running queries may fail. If you're fetching large datasets, use pagination and OData filters to reduce response times.&lt;/p&gt;

&lt;h3&gt;
  
  
  OData filtering
&lt;/h3&gt;

&lt;p&gt;The API supports OData query parameters for filtering results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;orderby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Number&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;GeneralLedger&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s1"&gt;'Asset'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field names are case-sensitive. A common mistake is using &lt;code&gt;number&lt;/code&gt; instead of &lt;code&gt;Number&lt;/code&gt; or &lt;code&gt;isActive&lt;/code&gt; instead of &lt;code&gt;IsActive&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deprecation: AccountRight Classic
&lt;/h2&gt;

&lt;p&gt;MYOB is retiring AccountRight Classic (v19 and earlier) on February 28, 2026. After this date, Classic company files become read-only. Users won't be able to create transactions, only view historical data.&lt;/p&gt;

&lt;p&gt;This matters for your integration if you support customers running older AccountRight versions. After February 2026, those files will not accept new invoices or payments via the API. Affected customers need to migrate to the current AccountRight or MYOB Business product.&lt;/p&gt;

&lt;p&gt;The API for modern AccountRight and MYOB Business is unaffected. This deprecation only impacts the legacy Classic versions that predated the current API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your integration
&lt;/h2&gt;

&lt;p&gt;MYOB does not provide sandbox environments with demo data. You need to set up test data yourself, which requires understanding both the API and MYOB's accounting workflows.&lt;/p&gt;

&lt;p&gt;Your options: create a trial MYOB Business account and populate it with test data manually, use a dedicated company file in a customer's MYOB system (with their permission), or set up a local AccountRight installation for development.&lt;/p&gt;

&lt;p&gt;Start with read operations to verify your authentication works. Fetch company info, then retrieve customers and accounts. Once reads work, test creating a simple customer contact before moving to invoices.&lt;/p&gt;

&lt;p&gt;Use MYOB's Postman collection to experiment with API calls before writing code. The collection is available from the developer portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unified API approach
&lt;/h2&gt;

&lt;p&gt;Building direct MYOB integrations requires handling OAuth flows, token management, data normalization, and ongoing API maintenance. If MYOB updates their authentication requirements (as they did in March 2025), you need to update your integration.&lt;/p&gt;

&lt;p&gt;A unified API provider like Apideck handles this complexity. Instead of building separate integrations for each accounting platform your customers use, you build one integration against a normalized API. The provider manages authentication, handles API updates, and normalizes data structures across platforms. For a detailed comparison of approaches, see our guide on &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integrations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach works well when you need to support multiple accounting platforms or when you want to reduce ongoing maintenance burden. For teams focused on building core product features rather than becoming accounting API experts, unified APIs can significantly reduce integration time.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Can I access multiple company files with one integration?
&lt;/h3&gt;

&lt;p&gt;Yes. Each company file requires separate OAuth authorization, but your integration can store multiple sets of tokens and company file GUIDs. When making API calls, use the appropriate token and businessId for the target company file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does MYOB support webhooks?
&lt;/h3&gt;

&lt;p&gt;No. The API does not provide webhooks for real-time notifications. You need to poll endpoints to detect changes. Use the &lt;code&gt;$filter&lt;/code&gt; parameter with timestamps to fetch only modified records since your last sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when AccountRight releases a new version?
&lt;/h3&gt;

&lt;p&gt;MYOB releases AccountRight updates regularly, typically monthly. Each release includes a decommission date for older versions, usually 2-3 months after the new release. Plan to update your integration testing when new versions ship, as some API behaviors may change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I email invoices through the API?
&lt;/h3&gt;

&lt;p&gt;Yes. Endpoints like &lt;code&gt;/Sale/Invoice/Item/{invoice_uid}/email&lt;/code&gt; let you trigger invoice emails directly from the API. The company file must be configured to send emails via AccountRight, and this only works for online files.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between AccountRight and MYOB Business?
&lt;/h3&gt;

&lt;p&gt;MYOB Business is the current product branding that replaces AccountRight and Essentials for new customers. The API is the same. AccountRight files show &lt;code&gt;UIAccessFlags: 3&lt;/code&gt; while Essentials files show &lt;code&gt;UIAccessFlags: 2&lt;/code&gt;. Your integration should work with both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Register for a &lt;a href="https://developer.myob.com" rel="noopener noreferrer"&gt;MYOB Developer account&lt;/a&gt;, create an API key, and start with the authentication flow. Read the endpoint documentation for contacts and invoices, since those are the most common integration points.&lt;/p&gt;

&lt;p&gt;If you want to accelerate your timeline, &lt;a href="https://www.apideck.com/signup" rel="noopener noreferrer"&gt;start with Apideck&lt;/a&gt; to connect MYOB and other accounting platforms through a single unified API.&lt;/p&gt;

&lt;p&gt;For more on building accounting integrations, see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate with in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/the-complete-guide-to-accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;The Complete Guide to Accounting API Integrations for Fintech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/how-to-integrate-sage-business-cloud-accounting-api" rel="noopener noreferrer"&gt;How to Integrate Sage Business Cloud Accounting API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>fintech</category>
      <category>saas</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Integrate with the Odoo API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:08:39 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-odoo-api-jp1</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-odoo-api-jp1</guid>
      <description>&lt;h2&gt;
  
  
  How Odoo's API Works
&lt;/h2&gt;

&lt;p&gt;Odoo exposes three API protocols: XML-RPC, JSON-RPC, and a REST API (added in Odoo 17). All three access the same underlying ORM layer — the same models, fields, and methods you'd use in a Python module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deprecation warning:&lt;/strong&gt; Odoo has announced that both XML-RPC (&lt;code&gt;/xmlrpc&lt;/code&gt;, &lt;code&gt;/xmlrpc/2&lt;/code&gt;) and JSON-RPC (&lt;code&gt;/jsonrpc&lt;/code&gt;) are scheduled for removal in Odoo 20 (targeted for fall 2026). Odoo 19 introduced a replacement called the JSON-2 API, which uses bearer token authentication and standard HTTP conventions. If you're starting a new integration today targeting Odoo 17+, it's worth checking the JSON-2 API docs before committing to XML-RPC. For integrations that need to support older versions (14–18), XML-RPC remains the most compatible choice.&lt;/p&gt;

&lt;p&gt;The key thing to understand regardless of protocol: Odoo's API is model-driven. Almost everything is a CRUD operation against a named model — &lt;code&gt;account.move&lt;/code&gt; for invoices, &lt;code&gt;res.partner&lt;/code&gt; for contacts, &lt;code&gt;account.account&lt;/code&gt; for the chart of accounts. Once you understand this pattern, working with any Odoo module follows the same playbook.&lt;/p&gt;

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

&lt;p&gt;Odoo uses a two-step authentication process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call the &lt;code&gt;authenticate&lt;/code&gt; method to get a user ID (uid)&lt;/li&gt;
&lt;li&gt;Use that uid plus your API key or password on all subsequent calls
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourodoo.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key_or_password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Use API keys in production
&lt;/span&gt;
&lt;span class="c1"&gt;# Step 1: Get the uid
&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/common&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Create the models proxy
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API keys vs passwords.&lt;/strong&gt; Odoo 14+ supports dedicated API keys (Settings &amp;gt; Technical &amp;gt; API Keys). Always use an API key in production rather than a user password. API keys can be scoped and revoked independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The uid is per-session.&lt;/strong&gt; It's a stable identifier for the user account, not a session token that expires. You can store it and reuse it, but re-authenticate if you switch credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access rights matter.&lt;/strong&gt; The uid you authenticate with determines what models and records you can read or write. If you're hitting access errors, check the user's security groups in Odoo — not just the API code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core API Methods
&lt;/h2&gt;

&lt;p&gt;Every Odoo model supports the same set of methods. These five cover the vast majority of integration use cases:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;search_read&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The workhorse method. Searches records matching a domain filter and returns field values in a single call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get all posted (validated) customer invoices from the last 30 days
&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-02-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always specify &lt;code&gt;fields&lt;/code&gt; explicitly. Omitting it returns every field on the model, which is slow and produces a lot of noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;create&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Creates one record and returns its ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-03-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Consulting services - March 2025&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[0, 0, {...}]&lt;/code&gt; syntax is Odoo's "One2many command" for creating nested records inline. It's unusual but consistent — you'll see it everywhere relational fields appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;write&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Updates one or more records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PO-2025-001&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;unlink&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Deletes records. Use with care — Odoo often prevents deletion of posted records to preserve audit trails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unlink&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;execute_kw&lt;/code&gt; with custom methods
&lt;/h3&gt;

&lt;p&gt;Some actions (like posting an invoice) aren't CRUD — they're state transitions. You call these as named methods on the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Post (validate) a draft invoice
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accounting Module: Key Models
&lt;/h2&gt;

&lt;p&gt;Here's a map of the models you'll use most when integrating with Odoo Accounting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoices and Bills — &lt;code&gt;account.move&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This single model handles customer invoices, vendor bills, credit notes, and journal entries. The &lt;code&gt;move_type&lt;/code&gt; field distinguishes them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;move_type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_invoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_invoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor bill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_refund&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer credit note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_refund&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor credit note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_receipt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sales receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_receipt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Purchase receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;entry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Journal entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key fields to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# Invoice number (e.g. INV/2025/0042)
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# See table above
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# draft, posted, cancel
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# not_paid, in_payment, paid, partial, reversed
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# Customer or vendor
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date_due&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_untaxed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_tax&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_residual&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Outstanding balance
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# External reference / PO number
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoice Lines — &lt;code&gt;account.move.line&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Individual line items on an invoice. You can query these directly for detailed reporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move.line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_subtotal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tax_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Payments — &lt;code&gt;account.payment&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Records of money in/out. When a payment is registered against an invoice, Odoo creates a reconciliation between the payment and the invoice line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get all customer payments
&lt;/span&gt;&lt;span class="n"&gt;payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inbound&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To register a payment against a specific invoice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create payment
&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inbound&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2025-03-10&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;journal_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ref&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;INV/2025/0042&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Validate the payment
&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.payment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;payment_id&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chart of Accounts — &lt;code&gt;account.account&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deprecated&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reconcile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;account_type&lt;/code&gt; field (Odoo 16+) replaced the old &lt;code&gt;user_type_id&lt;/code&gt; relation. The full list of values is: &lt;code&gt;asset_receivable&lt;/code&gt;, &lt;code&gt;asset_cash&lt;/code&gt;, &lt;code&gt;asset_current&lt;/code&gt;, &lt;code&gt;asset_non_current&lt;/code&gt;, &lt;code&gt;asset_prepayments&lt;/code&gt;, &lt;code&gt;asset_fixed&lt;/code&gt;, &lt;code&gt;liability_payable&lt;/code&gt;, &lt;code&gt;liability_credit_card&lt;/code&gt;, &lt;code&gt;liability_current&lt;/code&gt;, &lt;code&gt;liability_non_current&lt;/code&gt;, &lt;code&gt;equity&lt;/code&gt;, &lt;code&gt;equity_unaffected&lt;/code&gt;, &lt;code&gt;income&lt;/code&gt;, &lt;code&gt;income_other&lt;/code&gt;, &lt;code&gt;expense&lt;/code&gt;, &lt;code&gt;expense_depreciation&lt;/code&gt;, &lt;code&gt;expense_direct_cost&lt;/code&gt;, &lt;code&gt;off_balance&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contacts — &lt;code&gt;res.partner&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Both customers and vendors live here. The &lt;code&gt;customer_rank&lt;/code&gt; and &lt;code&gt;supplier_rank&lt;/code&gt; fields indicate their commercial relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;res.partner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer_rank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;street&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;country_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;property_payment_term_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with the JSON-RPC Interface
&lt;/h2&gt;

&lt;p&gt;If you prefer JSON (or you're working in a language without a mature XML-RPC client), the JSON-RPC endpoint works just as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/web/dataset/call_kw`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Authenticate&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;odooRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;execute_kw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account.move&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;move_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out_invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]]],&lt;/span&gt;
    &lt;span class="na"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partner_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount_total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with the Native REST API (Odoo 17+)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This section covers the built-in REST API shipped with Odoo 17 and 18 — not any of the third-party REST modules available on the Odoo App Store. The native REST API is experimental as of Odoo 18 and has limited official documentation. For production integrations targeting Odoo 16 or earlier, use XML-RPC or JSON-RPC instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;The REST API authenticates using your API key as a bearer token. Generate one in Odoo under Preferences &amp;gt; Account Security &amp;gt; New API Key, then pass it in every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;your_api_key&amp;gt;
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Odoo instance hosts multiple databases, add the database name as a header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Odoo-Database: your_database_name
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reading Records (GET)
&lt;/h3&gt;



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

&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourodoo.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Get all posted customer invoices
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;],[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating Records (POST)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-03-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_line_ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Consulting services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;new_invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;invoice_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_invoice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling Methods
&lt;/h3&gt;

&lt;p&gt;State transitions like posting an invoice require calling a named method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/account.move/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/action_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The JSON-2 API (Odoo 19+)
&lt;/h3&gt;

&lt;p&gt;Odoo 19 introduced a cleaner replacement for XML-RPC and JSON-RPC called the JSON-2 API, located at &lt;code&gt;/json/2/{model}/{method}&lt;/code&gt;. It uses the same bearer token auth but with named arguments in the request body instead of positional ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/json/2/account.move/search_read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Odoo-Database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_database_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON-2 API is the long-term direction for Odoo's external API. If you're on Odoo 19 or planning to migrate, it's worth building against this rather than XML-RPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination and Performance
&lt;/h2&gt;

&lt;p&gt;Odoo doesn't paginate automatically. You control it with &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_invoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;all_invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;offset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id asc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;all_invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_invoices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few performance tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use &lt;code&gt;order: 'id asc'&lt;/code&gt; when paginating to ensure stable ordering&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;limit&lt;/code&gt; under 500 — larger batches increase memory pressure on the server&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;search&lt;/code&gt; (returns IDs only) then &lt;code&gt;read&lt;/code&gt; (fetches specific fields by IDs) instead of &lt;code&gt;search_read&lt;/code&gt; when you need to pre-filter a large set before loading fields&lt;/li&gt;
&lt;li&gt;Avoid fetching binary fields (like &lt;code&gt;image_1920&lt;/code&gt;) unless you actually need them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling Odoo Versions
&lt;/h2&gt;

&lt;p&gt;Odoo's API surface changes between versions. The main gotchas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 14 to 16:&lt;/strong&gt; The account type system changed from a relational &lt;code&gt;user_type_id&lt;/code&gt; field to a direct &lt;code&gt;account_type&lt;/code&gt; string field. If you support multiple Odoo versions, you'll need to branch on this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 17:&lt;/strong&gt; A REST API was added alongside the existing XML-RPC and JSON-RPC protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 19+:&lt;/strong&gt; Odoo announced the deprecation of both the XML-RPC and JSON-RPC endpoints, with removal planned for Odoo 20 (fall 2026). Odoo 19 introduced a JSON-2 API as the replacement.&lt;/p&gt;

&lt;p&gt;You can check the server version via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;version_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Returns e.g. {'server_version': '16.0', 'server_version_info': [16, 0, 0, 'final', 0, '']}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Odoo returns errors inside the XML-RPC fault mechanism or the JSON-RPC error field. In Python, XML-RPC errors surface as &lt;code&gt;xmlrpc.client.Fault&lt;/code&gt; exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fault&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Odoo error &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;faultCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;faultString&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common error patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AccessError&lt;/code&gt; — the user doesn't have permission on that model or record&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValidationError&lt;/code&gt; — a required field is missing or a constraint failed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UserError&lt;/code&gt; — a business rule blocked the operation (e.g. trying to delete a posted invoice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always log the full &lt;code&gt;faultString&lt;/code&gt; — Odoo includes a Python traceback in it, which makes debugging much faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks and Real-time Sync
&lt;/h2&gt;

&lt;p&gt;Odoo doesn't have native webhooks in its community edition. For real-time sync, your options are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Polling&lt;/strong&gt; — query for records updated since a timestamp using &lt;code&gt;write_date&lt;/code&gt; or &lt;code&gt;create_date&lt;/code&gt; fields&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Odoo Automation&lt;/strong&gt; — create a scheduled action or server action that calls an external URL when a record changes (Enterprise or custom module)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database-level triggers&lt;/strong&gt; — only viable for self-hosted deployments where you have database access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Odoo Studio&lt;/strong&gt; — Enterprise feature that can configure webhook-style integrations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most integrations, polling with &lt;code&gt;write_date&lt;/code&gt; filtering works well enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;updated_invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;move_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;out_invoice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payment_state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using a Unified API Instead
&lt;/h2&gt;

&lt;p&gt;If you're building a product that needs to integrate with multiple accounting and ERP systems (QuickBooks, Xero, NetSuite, Sage, Microsoft Business Central), maintaining direct API integrations with each platform is a significant ongoing investment. Each one has different auth flows, data models, pagination patterns, and versioning behavior.&lt;/p&gt;

&lt;p&gt;A unified &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting API&lt;/a&gt; like Apideck normalizes these differences behind a single endpoint. You write the integration once to a standardized &lt;code&gt;Invoice&lt;/code&gt;, &lt;code&gt;Payment&lt;/code&gt;, or &lt;code&gt;Ledger Account&lt;/code&gt; model, and the platform handles the per-connector translation. This is especially useful if you're targeting vertical SaaS, embedded finance, or any segment where your customers run a mix of accounting platforms.&lt;/p&gt;

&lt;p&gt;Apideck supports &lt;a href="https://www.apideck.com/integrations/odoo" rel="noopener noreferrer"&gt;Odoo as a connector&lt;/a&gt; on both the Unified Accounting API and the CRM API, with 29+ normalized data models. On the accounting side, the connector covers invoices, bills, payments, bill payments, credit notes, journal entries, ledger accounts, tax rates, customers, suppliers, invoice items, company info, purchase orders, expenses, bank accounts, bank feed accounts, bank feed statements, subsidiaries, departments, locations, tracking categories, and attachments. The CRM side adds companies, contacts, leads, opportunities, activities, notes, and users.&lt;/p&gt;

&lt;p&gt;You can include Odoo alongside QuickBooks, Xero, and the rest without maintaining a separate direct integration, and without giving up coverage of the resources that matter for real accounting workflows.&lt;/p&gt;

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

&lt;p&gt;Odoo's API is mature, consistent, and surprisingly powerful once you get past the initial learning curve. The model-driven RPC approach means that anything you can do in the UI, you can do via API — including custom fields and modules added by your customers.&lt;/p&gt;

&lt;p&gt;The main things to keep in mind as you build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate with API keys, not passwords, in production&lt;/li&gt;
&lt;li&gt;Learn the domain filter syntax — it's the same across every model and covers complex queries&lt;/li&gt;
&lt;li&gt;Always specify &lt;code&gt;fields&lt;/code&gt; in &lt;code&gt;search_read&lt;/code&gt; to avoid pulling unnecessary data&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;write_date&lt;/code&gt; filtering for incremental sync rather than full refreshes&lt;/li&gt;
&lt;li&gt;Handle Odoo version differences explicitly, especially around the accounting type system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're integrating with Odoo as part of a broader multi-ERP strategy, it's worth evaluating whether a unified API layer saves you time versus owning each integration directly.&lt;/p&gt;

</description>
      <category>api</category>
      <category>odooapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Financial Statement APIs: What Most Accounting Platforms Won't Give You (and How to Get It Anyway)</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Wed, 01 Jul 2026 14:12:38 +0000</pubDate>
      <link>https://dev.to/apideck/financial-statement-apis-what-most-accounting-platforms-wont-give-you-and-how-to-get-it-anyway-3b7h</link>
      <guid>https://dev.to/apideck/financial-statement-apis-what-most-accounting-platforms-wont-give-you-and-how-to-get-it-anyway-3b7h</guid>
      <description>&lt;p&gt;Most accounting APIs will let you create an invoice, fetch a list of customers, or record a journal entry. But ask for a balance sheet or a trial balance, and things get complicated fast.&lt;/p&gt;

&lt;p&gt;Financial statements are the output every finance team cares about most. They're what lenders use to assess creditworthiness, what FP&amp;amp;A tools use to build forecasts, and what auditors use to verify the books. Yet the APIs behind popular accounting platforms treat financial statements as an afterthought, if they expose them at all.&lt;/p&gt;

&lt;p&gt;If you're building a SaaS product that needs financial statement data from your customers' accounting systems, you need to understand which statement types are available and how to work around the gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full picture: types of financial statements
&lt;/h2&gt;

&lt;p&gt;Before getting into API availability, it helps to be specific about what "financial statements" actually means. Five statement types matter for most integration use cases, and they vary wildly in how well accounting APIs support them.&lt;/p&gt;

&lt;p&gt;P&amp;amp;L statements and balance sheets sit at the top of the availability spectrum. Most major accounting platforms expose some form of endpoint for these two reports, though the depth and format differ. P&amp;amp;L shows revenue and expenses over a period, netting out to income or loss. Balance sheets capture a company's financial position (what it owns versus what it owes) at a specific point in time.&lt;/p&gt;

&lt;p&gt;Cash flow statements and trial balances are where things get thin. A cash flow statement reclassifies accrual-basis data into standardized cash flow categories. Xero's &lt;a href="https://developer.xero.com/documentation/api/finance/financialstatements" rel="noopener noreferrer"&gt;Finance API&lt;/a&gt; exposes a cash flow endpoint but restricts it to non-U.S. entities. QuickBooks Online has a cash flow report, but the response format is designed for HTML rendering rather than programmatic use. Many platforms simply don't offer it. Trial balances, which list every account with its debit or credit balance, face a similar problem: some platforms expose them as reports, others expect you to reconstruct them from ledger account data.&lt;/p&gt;

&lt;p&gt;The statement of changes in equity sits at the bottom of the stack. It tracks how equity changed over a period through items like net income and dividends. Very few accounting APIs expose this as a dedicated endpoint. In most cases, you'd derive it from balance sheet snapshots at two points in time.&lt;/p&gt;

&lt;p&gt;The pattern: the further you get from P&amp;amp;L and balance sheet, the thinner API support becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the gaps exist
&lt;/h2&gt;

&lt;p&gt;An accounting API that gives you access to invoices, bills, and the rest of the general ledger is giving you the raw ingredients. A financial statement is the cooked meal. The distance between those two things is significant.&lt;/p&gt;

&lt;p&gt;Financial statements require aggregation logic that varies by report type and accounting method (cash vs. accrual), plus date range and chart of accounts structure. Most accounting platforms built their APIs around CRUD operations on individual records, not around computed reports. Adding report endpoints means exposing business logic that was originally designed for the platform's own UI, and each provider has done this differently, if they've done it at all.&lt;/p&gt;

&lt;p&gt;The endpoint fragmentation becomes obvious once you look at the actual APIs. &lt;a href="https://developer.intuit.com/app/developer/qbo/docs/workflows/run-reports" rel="noopener noreferrer"&gt;QuickBooks Online&lt;/a&gt; exposes reports at a company-specific path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v3/company/&amp;lt;realmID&amp;gt;/reports/ProfitAndLoss?start_date=2025-01-01&amp;amp;end_date=2025-12-31
GET /v3/company/&amp;lt;realmID&amp;gt;/reports/BalanceSheet?date=2025-12-31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is a recursive row/column structure with headers and data cells mixed into a single nested array. It was designed for rendering HTML tables in the QuickBooks UI, not for programmatic consumption. Parsing it into a usable data structure requires custom logic for every report type.&lt;/p&gt;

&lt;p&gt;Xero splits financial statement data across two separate APIs. The standard &lt;a href="https://developer.xero.com/documentation/api/accounting/reports" rel="noopener noreferrer"&gt;Accounting API&lt;/a&gt; has report endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api.xro/2.0/Reports/ProfitAndLoss?fromDate=2025-01-01&amp;amp;toDate=2025-12-31
GET /api.xro/2.0/Reports/BalanceSheet?date=2025-12-31
GET /api.xro/2.0/Reports/TrialBalance?date=2025-12-31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Finance API (which requires separate partnership approval) exposes a different set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /finance.xro/1.0/financialstatements/profitandloss
GET /finance.xro/1.0/financialstatements/balancesheet
GET /finance.xro/1.0/financialstatements/cashflow  // not available for U.S. entities
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two APIs return different response schemas for the same data. And the Finance API's cash flow endpoint doesn't work for U.S. organizations at all.&lt;/p&gt;

&lt;p&gt;NetSuite buries financial reports behind SuiteQL or saved search configurations that require significant setup. Sage Intacct requires you to define report templates before you can pull data. Each platform has its own authentication flow and its own response schema.&lt;/p&gt;

&lt;p&gt;If you're building integrations with multiple accounting platforms, you're writing and maintaining entirely separate parsing logic for each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real use cases that depend on clean financial statement data
&lt;/h2&gt;

&lt;p&gt;The demand for financial statement APIs comes from a few distinct product categories.&lt;/p&gt;

&lt;p&gt;Lending and underwriting platforms are probably the most obvious consumer. They pull P&amp;amp;L statements and balance sheets to &lt;a href="https://www.apideck.com/use-cases/business-lending-loan-underwriting" rel="noopener noreferrer"&gt;assess creditworthiness&lt;/a&gt;. A lender evaluating a small business needs gross margin trends and current ratio computed from financial statement data. Manual document collection (requesting PDFs over email and re-keying numbers into a spreadsheet) adds days to the underwriting process. API access cuts that to minutes. Some lenders also pull trial balances for deeper account-level analysis during due diligence.&lt;/p&gt;

&lt;p&gt;Financial planning tools are another major category. &lt;a href="https://causal.app" rel="noopener noreferrer"&gt;Causal&lt;/a&gt;, a financial planning platform, integrated with customers' accounting systems to pull financial statement data into an AI-powered modeling tool. Users connect their QuickBooks or Xero account, and the platform auto-generates financial models with metrics like burn rate and runway, updated on a recurring schedule. Cash flow data is especially valuable here: forecasting tools that can ingest historical cash flow statements produce meaningfully better projections than those working from P&amp;amp;L alone.&lt;/p&gt;

&lt;p&gt;Then there's the growing segment of vertical SaaS products that &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;embed financial dashboards&lt;/a&gt; directly in their UI. A construction management platform might display a contractor's cash position alongside project data. A healthcare practice management tool might show monthly P&amp;amp;L trends next to patient volume. In both cases, the product team needs financial statement data arriving in a predictable format, on a reliable schedule, from whatever accounting system the customer happens to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it yourself vs. using a unified API
&lt;/h2&gt;

&lt;p&gt;If you only need to support one accounting platform, building a direct integration for financial statements is feasible, if tedious. You'll parse the provider's report format, map it to your internal schema, handle rate limits, and maintain it as the provider's API evolves. (For a full breakdown of &lt;a href="https://www.apideck.com/blog/the-complete-guide-to-accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;what that build process looks like&lt;/a&gt;, including authentication, sync patterns, and scaling, see the complete fintech accounting API guide.)&lt;/p&gt;

&lt;p&gt;The math changes when you need to support five or ten or twenty platforms. Each one has different authentication flows (OAuth 2.0 is standard, but implementation details vary widely) and different data models, with varying levels of financial statement support. Some platforms version their APIs aggressively; Xero has deprecated and replaced major endpoints multiple times. Others, like &lt;a href="https://www.apideck.com/blog/build-an-integration-with-quickbooks-desktop-in-2025" rel="noopener noreferrer"&gt;QuickBooks Desktop&lt;/a&gt;, still rely on SOAP-based communication through a local Web Connector.&lt;/p&gt;

&lt;p&gt;A unified API normalizes these differences behind a single interface. You build one integration, and the unified API provider handles the per-platform mapping and ongoing maintenance.&lt;/p&gt;

&lt;p&gt;Compare the endpoint fragmentation above with &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt;, which covers the two most requested financial statement types natively. Balance Sheet and Profit and Loss endpoints work across &lt;a href="https://developers.apideck.com/apis/accounting" rel="noopener noreferrer"&gt;45+ accounting connectors&lt;/a&gt;, with a consistent schema regardless of the downstream platform. Aging reports (Aged Creditors and Aged Debtors) round out the reporting layer. Instead of writing separate parsing logic for QuickBooks' nested row structure and Xero's report schema, you write one call:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="c1"&gt;// Pull a balance sheet from any connected accounting platform&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balanceSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or 'xero', 'sage-intacct', etc.&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;periodCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;periodType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For statement types that fall outside the unified schema (trial balance, cash flow, equity changes), Apideck's Proxy feature fills the gap. The Proxy lets you make native API calls to the downstream accounting platform while still using Apideck's managed authentication and connection infrastructure. You get the benefit of not having to handle OAuth flows or token refresh for each provider, while accessing any endpoint the provider exposes, including report types that aren't part of the unified model.&lt;/p&gt;

&lt;p&gt;In practice, this means your integration architecture can use unified endpoints for P&amp;amp;L and balance sheet data (where you get normalized schemas across all connectors) and fall back to Proxy calls for provider-specific reports like trial balances or cash flow statements. You handle two code paths instead of twenty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to evaluate in a financial statement API provider
&lt;/h2&gt;

&lt;p&gt;Not every unified API covers financial statements with the same depth. When you're evaluating options, a few things matter more than they might initially seem.&lt;/p&gt;

&lt;p&gt;Connector coverage for report endpoints is the first thing to check. A provider might support 20 accounting platforms for invoices and bills but only three for balance sheets. Financial statement support is often the last thing to ship because it requires more complex mapping.&lt;/p&gt;

&lt;p&gt;Filtering and date range support also matter more than you'd expect. Can you request a P&amp;amp;L for a specific quarter? A balance sheet as of a specific date? For forecasting and trend analysis, historical snapshots are critical.&lt;/p&gt;

&lt;p&gt;Schema depth determines how useful the data actually is once it arrives. Does the unified schema preserve the chart of accounts hierarchy? Can you distinguish between current and non-current assets? Flattened schemas lose information that matters for financial analysis.&lt;/p&gt;

&lt;p&gt;Then there's data freshness. Some unified APIs cache data and sync on a schedule. Others pass requests through to the source API in real time. For financial statements that inform lending decisions, stale data is a real risk. Apideck takes the real-time approach: API calls are processed and passed directly from source to your app, with no data stored in between.&lt;/p&gt;

&lt;p&gt;Finally, check whether the provider offers a passthrough or proxy mechanism for report types outside the unified schema. If your product only needs P&amp;amp;L and balance sheets today but might need trial balances or cash flow statements tomorrow, a provider with a strong proxy layer saves you from having to build and maintain a separate direct integration later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is heading
&lt;/h2&gt;

&lt;p&gt;Financial statement data is some of the most valuable information locked inside your customers' accounting systems. Getting to it programmatically is harder than it should be, because most accounting platforms designed their APIs around transactions, not reports. And the problem gets worse the further you move beyond P&amp;amp;L and balance sheets into trial balances and cash flow reporting.&lt;/p&gt;

&lt;p&gt;You can build custom report-parsing logic for every platform and every statement type you need to support. Or you can use a unified API that covers the core statement types natively and gives you a clean fallback for everything else.&lt;/p&gt;

&lt;p&gt;If your product depends on financial statement data from customers' accounting systems, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt; gives you normalized Balance Sheet and P&amp;amp;L endpoints across 45+ platforms, plus aging reports and a Proxy layer for accessing any provider-specific report endpoint. One integration, the full reporting picture.&lt;/p&gt;

</description>
      <category>api</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
      <category>financialapi</category>
    </item>
    <item>
      <title>How to Integrate With the Pennylane API</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 29 Jun 2026 11:05:57 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-pennylane-api-1kph</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-pennylane-api-1kph</guid>
      <description>&lt;p&gt;Pennylane is a financial management and accounting platform built for SMEs and accounting firms in France. It handles everything from invoice creation to VAT declarations, and its API lets you programmatically connect your own software to that accounting backbone. As one of the &lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;top accounting APIs&lt;/a&gt; gaining traction in the European market, it's worth understanding how it works under the hood.&lt;/p&gt;

&lt;p&gt;If you're building a product that touches invoicing, payments, or financial data for the French market, connecting to Pennylane is increasingly a requirement. Here's what you need to know to get started.&lt;/p&gt;

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

&lt;p&gt;Pennylane exposes two separate APIs depending on your use case:&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Company API&lt;/strong&gt; is designed for businesses that want to automate their own accounting workflows. Think: pushing invoices from your billing system into Pennylane, syncing supplier invoices, or creating ledger entries automatically. This falls squarely into the &lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;open accounting&lt;/a&gt; trend, where financial data flows freely between systems via APIs rather than manual exports.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Firm API&lt;/strong&gt; is for accounting firms managing multiple client companies. It provides access to client accounting data like trial balances, FEC exports, general ledgers, fiscal years, and client records.&lt;/p&gt;

&lt;p&gt;Both APIs are now on &lt;strong&gt;v2&lt;/strong&gt;, which is the official stable version. The v1 API has been deprecated, and Pennylane originally targeted end of 2025 for full removal. If you're starting a new integration, go straight to v2 as v1 will not receive any new features.&lt;/p&gt;

&lt;p&gt;In terms of what you can actually build, the v2 API supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating and importing customer invoices (both from structured data and from PDF)&lt;/li&gt;
&lt;li&gt;Importing supplier invoices&lt;/li&gt;
&lt;li&gt;Managing quotes, billing subscriptions, and commercial documents (purchase orders, proformas)&lt;/li&gt;
&lt;li&gt;Creating and reading ledger entries with analytical tags&lt;/li&gt;
&lt;li&gt;Creating and retrieving banking transactions&lt;/li&gt;
&lt;li&gt;Uploading file attachments (required for PDF-based invoice imports)&lt;/li&gt;
&lt;li&gt;Payment matching and reconciliation&lt;/li&gt;
&lt;li&gt;Tracking data changes via changelog endpoints for near real-time sync&lt;/li&gt;
&lt;li&gt;Managing customers, suppliers, products, and ledger accounts&lt;/li&gt;
&lt;li&gt;Importing Factur-X e-invoices (relevant for the upcoming French e-invoicing reform)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few things are not natively supported yet. Downpayment invoices can be created as a workaround by importing an invoice line with ledger account 4191, though they won't display as a downpayment in the Pennylane UI. Delivery notes are not available through the API.&lt;/p&gt;

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

&lt;p&gt;There are three authentication paths depending on who you are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Company API tokens&lt;/strong&gt; can be generated directly in Pennylane account settings. You need admin access (Executive, Internal Accountant, or External Accountant role). You can create multiple tokens per company, each with their own scopes and expiration settings. Tokens are not stored by Pennylane and can't be retrieved after creation, so store them securely on your side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firm API tokens&lt;/strong&gt; are generated from Firm account settings and give access to client data across the firm's portfolio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OAuth 2.0&lt;/strong&gt; is required for any integration that will be published on the Pennylane Marketplace or used across multiple companies. You'll need to contact &lt;a href="mailto:integrations@pennylane.com"&gt;integrations@pennylane.com&lt;/a&gt; to get registered. Access tokens expire after 24 hours by default, and refresh tokens are valid for 90 days. Once a refresh token is used, it's revoked and replaced with a new one, so make sure your token rotation logic handles this properly. (This is one of the areas where &lt;a href="https://www.apideck.com/blog/build-vs-buy-accounting-integrations" rel="noopener noreferrer"&gt;building vs buying accounting integrations&lt;/a&gt; becomes a real trade-off, since a unified API can handle token management for you.)&lt;/p&gt;

&lt;p&gt;All requests use Bearer token authentication in the Authorization header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;YOUR_API_KEY_OR_OAUTH_TOKEN&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base URL for v2 endpoints is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://app.pennylane.com/api/external/v2/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up a sandbox
&lt;/h2&gt;

&lt;p&gt;Before hitting production, you'll want a sandbox environment. For companies, you can create one directly from your Pennylane account by navigating to your profile and selecting "Test environment." For firms, you use your existing Firm account (no sandbox needed). Integrators need to email &lt;a href="mailto:integrations@pennylane.com"&gt;integrations@pennylane.com&lt;/a&gt; with their name and email for sandbox creation.&lt;/p&gt;

&lt;p&gt;The sandbox mirrors the production environment so you can test your full integration flow without affecting real data. If you plan to test invoicing, you'll also need to configure your invoice settings (company address, bank details, logo, and sequential numbering).&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your first request
&lt;/h2&gt;

&lt;p&gt;Start by verifying your credentials work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://app.pennylane.com/api/external/v2/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;YOUR_TOKEN&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response returns your user and company context, confirming authentication is working and you're hitting the right environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with invoices
&lt;/h2&gt;

&lt;p&gt;Invoicing is the most common &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integration&lt;/a&gt; use case, and Pennylane distinguishes between two approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating&lt;/strong&gt; an invoice means Pennylane generates the invoice document from structured data you provide (customer ID, line items, VAT rates, amounts). This is the right path when your system is the source of truth for what should be on the invoice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Importing&lt;/strong&gt; an invoice means you're pushing a pre-existing PDF into Pennylane. This requires a two-step process in v2: first upload the PDF via the &lt;code&gt;/file_attachments&lt;/code&gt; endpoint to get an attachment ID, then use that ID when calling the import endpoint. Only PDF files are accepted, up to 100 MB.&lt;/p&gt;

&lt;p&gt;Here's the general flow for creating a customer invoice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look up or create the customer via &lt;code&gt;/customers&lt;/code&gt; or &lt;code&gt;/company_customers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Optionally look up products via &lt;code&gt;/products&lt;/code&gt; for consistent pricing and VAT handling&lt;/li&gt;
&lt;li&gt;Optionally look up ledger accounts via &lt;code&gt;/ledger_accounts&lt;/code&gt; if you need specific account mappings&lt;/li&gt;
&lt;li&gt;POST to &lt;code&gt;/customer_invoices&lt;/code&gt; with the customer ID, line items, dates, and VAT rates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can create invoices in draft mode by passing &lt;code&gt;"draft": true&lt;/code&gt;, which is useful for review workflows before finalization. You can also convert a quote into an invoice directly via &lt;code&gt;/customer_invoices/create_from_quote&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One important detail: customer, product, and ledger account IDs are unique per company. If your integration serves multiple Pennylane companies, you need to store these mappings per tenant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination and filtering
&lt;/h2&gt;

&lt;p&gt;The v2 API uses cursor-based pagination instead of the offset-based pagination from v1. This is a meaningful change if you're migrating. Pennylane has a dedicated guide on implementing cursor-based pagination correctly.&lt;/p&gt;

&lt;p&gt;For filtering, the API uses a structured filter parameter in query strings. Filters follow a JSON format specifying the field, operator, and value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;?filter=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"external_reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ACME_001"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering is available on most list endpoints, including customer invoices, supplier invoices, draft invoices, credit notes, customers, suppliers, ledger accounts, ledger entries, ledger entry lines, products, and categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking changes
&lt;/h2&gt;

&lt;p&gt;The changelog endpoints are particularly useful for keeping your system in sync with Pennylane. They let you poll for creation, update, and deletion events on specific resources in near real-time. Currently supported for customer invoices and supplier invoices.&lt;/p&gt;

&lt;p&gt;When you receive change events, you'll need to make a follow-up request to fetch the complete resource data, since the changelog only tells you what changed, not the full payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits
&lt;/h2&gt;

&lt;p&gt;The v2 API allows up to 5 requests per second. Rate limiting applies on both production and sandbox environments across all endpoints. If you exceed the limit, you'll receive a 429 HTTP error.&lt;/p&gt;

&lt;p&gt;If you're processing high volumes (nightly invoice syncs, historical imports), you'll need proper queuing and retry logic with exponential backoff to handle 429 responses gracefully. This is a common challenge across &lt;a href="https://www.apideck.com/blog/the-complete-guide-to-accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;accounting API integrations for fintech&lt;/a&gt; products. Also worth noting: Pennylane does not enforce idempotency on creation endpoints like &lt;code&gt;/customer_invoices&lt;/code&gt; or &lt;code&gt;/ledger_entries&lt;/code&gt;, so duplicate requests will create duplicate records. Build deduplication logic on your side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key differences from v1
&lt;/h2&gt;

&lt;p&gt;If you're migrating from v1, a few structural changes to be aware of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal IDs only.&lt;/strong&gt; V2 uses Pennylane's internal IDs exclusively. You can no longer retrieve resources by &lt;code&gt;source_id&lt;/code&gt;. To help with migration, v1 responses include a &lt;code&gt;v2_id&lt;/code&gt; attribute so you can map old IDs to new ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Granular scopes.&lt;/strong&gt; V2 introduces more specific OAuth scopes, each with a readonly counterpart. You'll need to update your OAuth app configuration and re-authenticate to use v2 endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-resource creation.&lt;/strong&gt; V1 allowed creating multiple object types in a single call. V2 requires separate calls through dedicated endpoints, which simplifies error handling and debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File attachments as resources.&lt;/strong&gt; In v1, you could inline base64 file content or a URL directly in an invoice import call. In v2, file attachments are a separate resource that you create first and then reference by ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linked collections.&lt;/strong&gt; Where v1 returned nested arrays (like invoice lines inside an invoice), v2 returns a URL pointing to the collection endpoint instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standardized response bodies.&lt;/strong&gt; In v2, a data model like &lt;code&gt;customer_invoice&lt;/code&gt; always returns the same response attributes regardless of whether you're hitting the show or list endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Store IDs per tenant.&lt;/strong&gt; If your integration serves multiple companies, maintain a mapping of Pennylane IDs (customers, products, ledger accounts) for each company.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use external references.&lt;/strong&gt; When creating customers or other entities, populate the &lt;code&gt;external_reference&lt;/code&gt; field with your own system's ID. This makes lookups much easier later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default VAT and ledger mappings.&lt;/strong&gt; If you omit the &lt;code&gt;ledger_account_id&lt;/code&gt; when creating invoices, Pennylane applies the company's default chart of accounts and VAT settings. This can simplify your integration if you don't need fine-grained control over accounting mappings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor the changelog.&lt;/strong&gt; Pennylane's API evolves frequently with new endpoints and deprecations. Subscribe to their &lt;a href="https://pennylane.readme.io/changelog.rss" rel="noopener noreferrer"&gt;API changelog RSS feed&lt;/a&gt; to stay ahead of breaking changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan for role and plan requirements.&lt;/strong&gt; End users need at least a "gestionnaire" (manager) role and an Essential plan or higher to activate API integrations. Surface clear error messages if users lack the required permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate before you send.&lt;/strong&gt; Check totals and VAT consistency on your side before making API calls. Pennylane returns detailed error payloads with a &lt;code&gt;details&lt;/code&gt; object that tells you exactly which field caused the failure, but catching issues client-side saves round trips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a unified API instead
&lt;/h2&gt;

&lt;p&gt;If you're building integrations across multiple accounting platforms and not just Pennylane, building and maintaining direct integrations with each one adds up quickly. Each platform has its own authentication flows, data models, rate limits, and breaking changes to track. For a deeper look at this trade-off, see our guide on &lt;a href="https://www.apideck.com/blog/how-fintech-startups-can-integrate-with-20-plus-accounting-platforms-using-a-single-api" rel="noopener noreferrer"&gt;how fintech startups can integrate with 20+ accounting platforms using a single API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Unified Accounting API&lt;/a&gt; supports &lt;a href="https://www.apideck.com/integrations/pennylane" rel="noopener noreferrer"&gt;Pennylane&lt;/a&gt; as a native connector alongside &lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/blog/how-to-integrate-sage-business-cloud-accounting-api" rel="noopener noreferrer"&gt;Sage&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, and 45+ other accounting platforms through a single integration. The Pennylane connector covers 15+ normalized data models, including customers, suppliers, invoices, bills, ledger accounts, tax rates, products, ledger entries, transactions, quotes, and bank accounts. Authentication is handled via OAuth 2.0 with automatic token refresh, and real-time webhooks keep your data in sync without polling.&lt;/p&gt;

&lt;p&gt;This is especially relevant if your product needs to support both French and international accounting systems. Instead of managing Pennylane's OAuth token rotation, Xero's partner certification process, and QuickBooks' different API versions separately, a &lt;a href="https://www.apideck.com/blog/best-unified-api-platforms-for-developers-and-saas-teams" rel="noopener noreferrer"&gt;unified API platform&lt;/a&gt; gives you one integration point, one data schema, and one set of SDKs across all of them. Check the full list of &lt;a href="https://developers.apideck.com/apis/accounting" rel="noopener noreferrer"&gt;accounting connectors&lt;/a&gt; to see what's available.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/" rel="noopener noreferrer"&gt;Pennylane API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/docs/getting-started" rel="noopener noreferrer"&gt;Getting started guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/docs/supported-use-cases" rel="noopener noreferrer"&gt;Supported use cases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/docs/oauth-20-walkthrough" rel="noopener noreferrer"&gt;OAuth 2.0 walkthrough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/docs/api-v2-vs-v1" rel="noopener noreferrer"&gt;V1 to V2 migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/docs/rate-limiting-1" rel="noopener noreferrer"&gt;Rate limiting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pennylane.readme.io/changelog" rel="noopener noreferrer"&gt;API changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.apideck.com/apis/accounting/pennylane/connection" rel="noopener noreferrer"&gt;Apideck Pennylane connection guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>12 Best Practices for Accounting Integrations in Vertical SaaS</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 26 Jun 2026 11:26:46 +0000</pubDate>
      <link>https://dev.to/apideck/12-best-practices-for-accounting-integrations-in-vertical-saas-19dc</link>
      <guid>https://dev.to/apideck/12-best-practices-for-accounting-integrations-in-vertical-saas-19dc</guid>
      <description>&lt;p&gt;&lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;Accounting integrations&lt;/a&gt; fail not because accounting is complex, but because teams underestimate domain-specific workflows, platform differences, and long-term maintenance costs. The result is rework, data drift, and engineering resources permanently diverted from core product development.&lt;/p&gt;

&lt;p&gt;This article documents where vertical SaaS accounting integrations commonly break and the engineering patterns that prevent these failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start From Industry Workflows, Not the Ledger
&lt;/h2&gt;

&lt;p&gt;The most common mistake is modeling integrations around generic general ledger objects, such as accounts, journals, and invoices, instead of the domain language your customers actually use.&lt;/p&gt;

&lt;p&gt;Construction companies think in job costing and progress billing. Agencies operate on retainers and time-based billing. Marketplaces handle revenue sharing and payouts. Healthcare practices deal with insurance reconciliation and patient billing cycles. Each vertical moves money differently, and your integration needs to reflect that.&lt;/p&gt;

&lt;p&gt;When you build around generic GL concepts, you force users to mentally translate between their workflow and accounting primitives. The result is poor UX, low adoption, and constant support requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Map integrations to real workflows first. If your construction SaaS tracks job costs, the integration should sync job cost data, not just raw journal entries.&lt;/li&gt;
&lt;li&gt;Define the source of truth per object. Your system might own contracts and billing schedules while the accounting platform owns account balances and tax filings.&lt;/li&gt;
&lt;li&gt;Document edit authority across systems. Specify which fields can be modified where, and what happens when conflicts occur.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Narrow the First Use Case
&lt;/h2&gt;

&lt;p&gt;Full ledger sync sounds impressive in a product demo. In practice, it fails.&lt;/p&gt;

&lt;p&gt;Teams that try to replicate an entire accounting system in v1 encounter edge cases that derail the project: multi-currency transactions, prepayments, adjustments, voided invoices, partial payments, credit memos. Each one requires specific handling logic, and attempting all of them simultaneously means shipping nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with one value stream. For most vertical SaaS, this is approved invoices flowing to the ledger. That single flow delivers immediate value and establishes the integration pattern.&lt;/li&gt;
&lt;li&gt;Validate mappings with real customer data before expanding scope. Your assumptions about how customers use their accounting system are probably wrong.&lt;/li&gt;
&lt;li&gt;Add complexity incrementally based on actual usage. If 5% of customers use multi-currency, deprioritize it until you've nailed the core flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Respect Platform Differences
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/sage-intacct" rel="noopener noreferrer"&gt;Sage&lt;/a&gt;, and &lt;a href="https://www.apideck.com/integrations/freshbooks" rel="noopener noreferrer"&gt;FreshBooks&lt;/a&gt; are not interchangeable. Treating them as such creates reconciliation nightmares.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concrete examples of where platforms diverge:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tax handling&lt;/strong&gt;: QuickBooks Online requires tax codes on line items. Xero calculates tax at the invoice level. NetSuite supports tax groups and nexus rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit memos&lt;/strong&gt;: Some platforms link credit memos to original invoices. Others treat them as standalone documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment application&lt;/strong&gt;: The mechanics of applying payments to invoices differ significantly. Partial payments, overpayments, and unapplied cash all have platform-specific behaviors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chart of accounts&lt;/strong&gt;: Account types, numbering schemes, and hierarchy support vary widely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Naïve 1:1 field mappings produce data that technically syncs but doesn't reconcile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design a &lt;a href="https://www.apideck.com/blog/how-to-standardize-data-across-accounting-software-quickbooks-xero-netsuite-sage" rel="noopener noreferrer"&gt;normalized internal data model&lt;/a&gt; that captures the semantics your application needs.&lt;/li&gt;
&lt;li&gt;Build explicit transformation logic per connector. Don't hide platform differences. Handle them.&lt;/li&gt;
&lt;li&gt;Surface validation errors early and clearly. When a transaction can't be represented correctly in the target platform, tell the user before it becomes a reconciliation issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Use Robust Authentication and Tenant Isolation
&lt;/h2&gt;

&lt;p&gt;OAuth failures are the leading cause of integration downtime. Token expiry, scope mismatches, revoked access, and tenant leakage create both operational problems and financial risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common failure modes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens expire and the refresh mechanism fails silently. Sync stops, and no one notices until month-end close.&lt;/li&gt;
&lt;li&gt;Scope changes during re-authentication reduce permissions. The integration has write access on Monday and read-only access on Tuesday.&lt;/li&gt;
&lt;li&gt;Shared credential storage allows one tenant's token to access another tenant's data. This is a security incident waiting to happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; per provider with proper state management.&lt;/li&gt;
&lt;li&gt;Isolate tokens per tenant and per connector. One customer's QuickBooks connection should have no relationship to another's.&lt;/li&gt;
&lt;li&gt;Automate token refresh with monitoring. Alert when refresh fails, not when sync fails.&lt;/li&gt;
&lt;li&gt;Request only the scopes you need and validate them on each authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Design for Idempotency and Retry Safety
&lt;/h2&gt;

&lt;p&gt;Network failures happen. API rate limits trigger. Timeouts occur. Your integration will retry operations, and those retries must be safe.&lt;/p&gt;

&lt;p&gt;Financial side effects compound quickly. A duplicate invoice is annoying. A duplicate payment creates a real accounting discrepancy. Duplicate journal entries corrupt the ledger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use idempotency keys on all write operations. Most accounting APIs support them, so use them consistently.&lt;/li&gt;
&lt;li&gt;Maintain a mapping between your internal IDs and remote IDs. Before creating a new record, check if it already exists.&lt;/li&gt;
&lt;li&gt;Process operations through a queue with exponential backoff. Don't retry immediately on failure.&lt;/li&gt;
&lt;li&gt;Log enough context to diagnose retry failures. When an operation fails three times, you need to understand why.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Treat Webhooks as First-Class Infrastructure
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/what-is-a-webhook" rel="noopener noreferrer"&gt;Webhooks&lt;/a&gt; are how you learn about changes in the accounting system. Treating them as an afterthought causes data drift that's expensive to fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problems that emerge:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missed webhooks due to temporary downtime. Your system never learns about invoice updates.&lt;/li&gt;
&lt;li&gt;Duplicate webhooks from provider retry logic. You process the same event twice.&lt;/li&gt;
&lt;li&gt;Synchronous processing that blocks acknowledgment. The provider times out and retries, compounding the problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement the verify-enqueue-acknowledge pattern. Validate the webhook signature, write it to a queue, return 200 immediately. Process asynchronously.&lt;/li&gt;
&lt;li&gt;Deduplicate events using webhook IDs or event identifiers.&lt;/li&gt;
&lt;li&gt;Build dead-letter queues for events that fail processing. Don't lose them.&lt;/li&gt;
&lt;li&gt;Monitor event lag. If you're consistently behind, you have a scaling problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Build for Reconciliation and Auditability
&lt;/h2&gt;

&lt;p&gt;Accountants care more about explainability than speed. They need to close books monthly, answer auditor questions, and trace every transaction to its source.&lt;/p&gt;

&lt;p&gt;Black-box sync jobs that "just work" aren't good enough. When something doesn't reconcile, and something always doesn't reconcile, users need visibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement per-transaction traceability. Every synced record should link back to its source in your system and its destination in the accounting platform.&lt;/li&gt;
&lt;li&gt;Build sync status dashboards. Show what synced, what failed, and what's pending.&lt;/li&gt;
&lt;li&gt;Maintain detailed logs with timestamps, payloads, and response codes. But set appropriate retention limits; you don't need raw payloads from three years ago.&lt;/li&gt;
&lt;li&gt;Provide manual retry and skip controls. Sometimes the right answer is "don't sync this one."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Enforce Rigorous Data Validation
&lt;/h2&gt;

&lt;p&gt;The worst place to discover a validation error is in a cryptic API response from the accounting platform. By that point, you've already confused the user, created a support ticket, and potentially left data in an inconsistent state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common validation failures:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing required fields that vary by platform configuration&lt;/li&gt;
&lt;li&gt;Invalid account references (deleted accounts, wrong account types)&lt;/li&gt;
&lt;li&gt;Date formats that don't match platform locale settings&lt;/li&gt;
&lt;li&gt;Amount precision exceeding platform limits&lt;/li&gt;
&lt;li&gt;Line item totals that don't sum to invoice totals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-validate against known ledger rules before sending data. Catch errors in your system, not theirs.&lt;/li&gt;
&lt;li&gt;Provide human-readable error messages. "Invoice total must equal sum of line items" is useful. "422 Unprocessable Entity" is not.&lt;/li&gt;
&lt;li&gt;Build validation guidance into your product UI. If an invoice won't sync because the customer record is incomplete, show that in the invoice editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Plan for Connector Sprawl and Maintenance
&lt;/h2&gt;

&lt;p&gt;Building one accounting integration is a project. Maintaining ten is a product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-term realities that teams underestimate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API versioning and deprecation cycles. QuickBooks deprecated their v2 API. Xero has mandatory TLS requirements. NetSuite regularly updates their SOAP endpoints.&lt;/li&gt;
&lt;li&gt;Authentication changes. OAuth scope requirements evolve. Token lifetimes change. New security requirements emerge.&lt;/li&gt;
&lt;li&gt;Regional differences. UK VAT handling differs from US sales tax. Australian BAS reporting has specific requirements. Multi-entity support varies by platform and region.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each connector becomes a maintenance liability that consumes engineering time indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Abstract integrations behind a single internal interface. Your application code shouldn't know whether it's talking to QuickBooks or Xero.&lt;/li&gt;
&lt;li&gt;Reduce the blast radius of provider changes. When Xero updates their API, the fix should be isolated to one adapter.&lt;/li&gt;
&lt;li&gt;Evaluate &lt;a href="https://www.apideck.com/blog/best-unified-api-platforms-for-developers-and-saas-teams" rel="noopener noreferrer"&gt;unified API platforms&lt;/a&gt; for connectors outside your core differentiation. Building and maintaining 15 accounting connectors is rarely the best use of engineering resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Align Integrations With Monetization
&lt;/h2&gt;

&lt;p&gt;Accounting integrations often become cost centers: expensive to build, expensive to maintain, offered for free, and generating unlimited support tickets.&lt;/p&gt;

&lt;p&gt;This is a product strategy failure, not a technical one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package advanced integrations into premium tiers. Basic invoice sync might be included; multi-entity support, &lt;a href="https://www.apideck.com/custom-field-mapping" rel="noopener noreferrer"&gt;custom field mapping&lt;/a&gt;, and real-time sync can justify higher pricing.&lt;/li&gt;
&lt;li&gt;Tie integrations to revenue-generating workflows. If the integration enables faster payment collection, capture some of that value.&lt;/li&gt;
&lt;li&gt;Track integration usage and support costs. Know which integrations are worth maintaining and which are dragging down margins.&lt;/li&gt;
&lt;li&gt;Treat integrations as product surface area. They deserve roadmaps, success metrics, and deprecation policies like any other feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. Prioritize Security, Compliance, and Data Residency
&lt;/h2&gt;

&lt;p&gt;Accounting integrations expand your attack surface and regulatory exposure. Financial data is sensitive, and the systems you're connecting to contain complete records of your customers' business operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issues that surface late in development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data residency requirements. EU customers may require that their accounting data never touches US infrastructure.&lt;/li&gt;
&lt;li&gt;Credential storage risks. Compromised OAuth tokens provide direct access to customer financial systems.&lt;/li&gt;
&lt;li&gt;Audit requirements. SOC 2, PCI, and industry-specific frameworks all have implications for how you handle financial data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypt data at rest and in transit. This is table stakes.&lt;/li&gt;
&lt;li&gt;Implement role-based access controls. Not every employee needs access to integration credentials.&lt;/li&gt;
&lt;li&gt;Minimize data retention. Sync what you need, don't cache what you don't.&lt;/li&gt;
&lt;li&gt;Align with SOC 2 practices early. Retrofitting compliance is expensive.&lt;/li&gt;
&lt;li&gt;Document your data flows. Know where financial data goes and how long it stays there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. Invest in Developer and Admin UX
&lt;/h2&gt;

&lt;p&gt;Technically correct integrations that are painful to configure or debug don't get adopted. They generate support tickets, implementation delays, and churn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UX problems that kill adoption:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex OAuth flows that confuse non-technical users&lt;/li&gt;
&lt;li&gt;No visibility into sync status or errors&lt;/li&gt;
&lt;li&gt;Mapping interfaces that require accounting expertise to configure&lt;/li&gt;
&lt;li&gt;Missing sandbox or test modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build clear onboarding flows. Guide users through connection, configuration, and initial sync.&lt;/li&gt;
&lt;li&gt;Provide sandbox modes that connect to accounting platform sandbox environments. Let users test without risking production data.&lt;/li&gt;
&lt;li&gt;Create admin tools for mapping and diagnostics. When something breaks, admins should be able to investigate without contacting support.&lt;/li&gt;
&lt;li&gt;Write documentation that addresses real problems. Setup guides, troubleshooting steps, and mapping references.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These best practices align with &lt;a href="https://www.apideck.com/blog/what-is-open-accounting" rel="noopener noreferrer"&gt;open accounting principles&lt;/a&gt; — the idea that financial data should flow freely between systems through standardized APIs, enabling real-time visibility for all stakeholders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Accounting integrations&lt;/a&gt; are infrastructure, not features. They require the same architectural rigor as your database layer or authentication system. The patterns outlined here, such as domain-driven design, incremental scope, platform abstraction, idempotency, and operational visibility, aren't novel. They're what distinguishes integrations that scale from integrations that become permanent maintenance burdens.&lt;/p&gt;

&lt;p&gt;For teams that want to avoid building and maintaining dozens of direct accounting integrations, unified APIs like Apideck provide a standardized layer for authentication, normalized schemas, webhooks, and compliance. This allows engineering teams to focus on product logic instead of connector upkeep.&lt;/p&gt;

</description>
      <category>api</category>
      <category>apiintegration</category>
      <category>unifiedapi</category>
      <category>verticalsaas</category>
    </item>
    <item>
      <title>Xero Integrations: The Complete Guide to Integration with Xero (2026)</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Thu, 25 Jun 2026 13:15:44 +0000</pubDate>
      <link>https://dev.to/apideck/xero-integrations-the-complete-guide-to-integration-with-xero-2026-29ek</link>
      <guid>https://dev.to/apideck/xero-integrations-the-complete-guide-to-integration-with-xero-2026-29ek</guid>
      <description>&lt;p&gt;Integration with Xero is one of the most common requirements for SaaS products serving small and mid-sized businesses. Xero is cloud-based accounting software used by over 4.6 million subscribers across more than 180 countries. It dominates the SME market in the UK, Australia, and New Zealand, and is growing fast in the US. Its open API approach has made it one of the most integrated accounting platforms on the planet — with over 1,000 apps in its marketplace connecting to payroll, payments, eCommerce, CRM, expense management, and more.&lt;/p&gt;

&lt;p&gt;This guide covers the most important Xero integrations by category, how they work, what to look for when choosing one, and — for product teams building a Xero integration into their own software — what it actually takes to build one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Xero App Marketplace
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apps.xero.com/" rel="noopener noreferrer"&gt;Xero App Marketplace&lt;/a&gt; is Xero's official directory of third-party software that integrates with Xero. It's where businesses go to extend what Xero does — adding tools for payroll, inventory, payments, reporting, time tracking, and more — and where SaaS companies list their products to reach Xero's global customer base.&lt;/p&gt;

&lt;p&gt;As of 2026, the marketplace hosts over 1,000 apps across more than 30 categories. Listings are organized by use case, industry, and region, making it relatively easy for a small business owner to find tools relevant to their workflow. Each listing shows ratings, pricing, and which Xero plans the app is compatible with.&lt;/p&gt;

&lt;p&gt;To list in the marketplace, you need to become a certified Xero App Partner. That involves building a working integration, onboarding at least 3 active customers during a review window, and agreeing to Xero's commercial terms (which, as of March 2026, includes the new usage-based API pricing model rather than the previous revenue-share arrangement).&lt;/p&gt;

&lt;h3&gt;
  
  
  Xero by market
&lt;/h3&gt;

&lt;p&gt;Xero's global footprint is uneven by design. The platform originated in New Zealand in 2006 and expanded first to Australia and the UK before moving into North America and beyond. Its adoption patterns, dominant integrations, and even regulatory requirements differ meaningfully by region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Australia and New Zealand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is Xero's home market and where its penetration is deepest. Australian and New Zealand SMEs tend to be highly digitized in their accounting practices, and Xero holds the dominant position against QuickBooks and MYOB. Payroll compliance here is particularly complex — single touch payroll (STP) reporting is mandatory for all employers in Australia, which means payroll integrations like Employment Hero and KeyPay are especially important in this region. Bank feed connectivity is also a high priority, as reconciliation speed is a key selling point for accountants. Apps built for this market often need to support multi-currency for NZD/AUD and handle GST rather than VAT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;United Kingdom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The UK is Xero's largest market by subscriber count outside ANZ. Adoption accelerated significantly after Xero positioned itself as the cloud alternative to Sage and QuickBooks in the mid-2010s. UK-specific requirements include Making Tax Digital (MTD) compliance — Xero is MTD-compatible for VAT, which is a non-negotiable feature for any UK accounting software. Payroll integrations need to handle PAYE, National Insurance, and Real Time Information (RTI) submissions to HMRC. Direct debit via GoCardless is the dominant payment collection method for subscription businesses. The UK also has a strong ecosystem of accounting practices built on Xero, making accountant-facing tools (practice management, client reporting) disproportionately popular here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;United States&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Xero's fastest-growing but most contested market. QuickBooks Online holds an estimated 80% share of US small business accounting software, which means Xero is the challenger. Its US subscriber base skews toward businesses with international operations, founder-led tech companies, and businesses whose accountants actively recommend Xero as an alternative to Intuit's ecosystem. US-specific integrations cover federal and state payroll tax compliance (Gusto is the most widely used), sales tax automation (TaxJar, Avalara), and US bank feeds. The IRS 1099 reporting workflow is a common pain point for integrations in this market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canada&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A smaller but meaningful Xero market, where it competes primarily with QuickBooks and Sage 50. GST/HST compliance is the primary tax requirement, and Canadian payroll is handled by integrations like Wagepoint and Humi. The Quebec market adds complexity with French-language requirements and distinct provincial tax rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Europe (ex-UK)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Xero has limited but growing presence in continental Europe, primarily among English-speaking founders and international businesses. Compliance requirements vary significantly by country — VAT regimes, payroll rules, and e-invoicing mandates (increasingly common across the EU) are not standardized. This fragmentation makes the European market harder to serve without localized integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;South Africa and Southeast Asia&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Xero has a presence in South Africa and in markets like Singapore and Hong Kong, where English-language SMEs and accountants are comfortable with cloud-first tools. These markets tend to use the global app ecosystem rather than region-specific integrations, though local payroll and tax tools do exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is integration with Xero?
&lt;/h2&gt;

&lt;p&gt;Integration with Xero means connecting Xero to another software application so that data flows between the two systems automatically. Instead of manually exporting CSVs, copying invoice numbers, or reconciling data across tools, a Xero integration syncs that data in real time or on a defined schedule.&lt;/p&gt;

&lt;p&gt;For a business using Xero, integrations mean the accounting ledger stays current without manual intervention. For a SaaS company, offering integration with Xero means customers can adopt your product without abandoning their accounting workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Xero integrations matter
&lt;/h2&gt;

&lt;p&gt;Finance teams live in Xero. When your product doesn't connect to it, you create friction at exactly the wrong moment — during a sales demo, at onboarding, or the first time a customer asks "does this sync with our accounting system?"&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer errors.&lt;/strong&gt; Manual data entry between systems is one of the leading causes of accounting errors. Automated sync removes that entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster close.&lt;/strong&gt; Reconciliation takes hours when it's manual. With integrated bank feeds and automated transaction matching, finance teams close books in a fraction of the time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better visibility.&lt;/strong&gt; Real-time data across connected tools means CFOs and finance managers have current numbers, not last-week's export.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stronger retention.&lt;/strong&gt; For SaaS products, accounting integrations are a lock-in mechanism. Customers who've embedded your product into their financial workflows don't churn easily.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Xero integration categories
&lt;/h2&gt;

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

&lt;p&gt;Payment integrations connect Xero to your payment stack. Transactions get recorded automatically as they happen, matched to invoices, and reconciled in the ledger without manual entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripe&lt;/strong&gt; is the most commonly used. When a customer pays an invoice, Stripe pushes the transaction to Xero, marks the invoice as paid, and logs the payment in the correct account. It supports card payments, Apple Pay, Google Pay, and cross-border transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GoCardless&lt;/strong&gt; is popular for recurring billing and direct debit. Particularly common with subscription businesses in the UK and Australia where bank debit is the norm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PayPal&lt;/strong&gt; has a native Xero integration for businesses processing payments through PayPal — it reconciles transactions against Xero invoices automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expense management
&lt;/h3&gt;

&lt;p&gt;Expense integrations eliminate the paper trail. Employees capture receipts on mobile, expenses get categorized, and approved costs sync to Xero as journal entries or bills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Xero Expenses&lt;/strong&gt; is the native add-on built by Xero itself. From their phone, employees can log out-of-pocket expenses and snap a photo of each receipt. Claims are linked directly to the Xero account, so approvers can review and &lt;a href="https://www.apideck.com/use-cases/expense-management-integrations" rel="noopener noreferrer"&gt;reimburse employees&lt;/a&gt; without switching tools. Because it's built by Xero, the data mapping is exact — no translation layer, no sync errors. For companies that don't need comprehensive spend management or better company cards, it's a solid starting point that reduces expense errors and speeds up reimbursement.&lt;/p&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Employees snap photos of receipts and log mileage claims from their phone.&lt;/li&gt;
&lt;li&gt;Claims are processed and appear in the Xero ledger automatically.&lt;/li&gt;
&lt;li&gt;Approvers track due dates and reimburse employees faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ramp&lt;/strong&gt; integrates directly with Xero for corporate card spend. It syncs accounting fields, transactions, reimbursements, vendor bills, and credits bidirectionally in real time. Accounts payable, expense management, and travel booking consolidate in one tool that stays in sync with Xero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expensify&lt;/strong&gt; is one of the most widely used expense tools for Xero users. It handles receipt scanning, mileage tracking, and approval workflows, then pushes approved reports to Xero with correct GL coding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spendesk&lt;/strong&gt; connects to Xero as part of a procure-to-pay workflow rather than a standalone expense sync: GL codes and tracking categories are assigned at the point of purchase approval, so the data arriving in Xero is already structured and policy-compliant before reconciliation begins. The integration covers card transactions, supplier invoices, and employee reimbursements, with three-way matching confirming PO-to-invoice alignment before payment is released via SEPA or Faster Payments. Invoices are archived in HMRC-compliant format automatically. Spendesk serves 200,000+ users across 29 EEA countries and the UK, and also integrates with QuickBooks, Sage, NetSuite, and DATEV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dext&lt;/strong&gt; (formerly Receipt Bank) focuses specifically on document capture — bills, receipts, invoices — and extracts data to push into Xero before human review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payroll
&lt;/h3&gt;

&lt;p&gt;Payroll integrations keep wage payments and tax withholdings accurate in the ledger without manual journal entries after every pay run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gusto&lt;/strong&gt; syncs payroll transactions to Xero every time a payroll is run. You can choose how data appears — by employee, department, job title, or project. It handles US payroll, tax filings, and contractor payments. Pricing starts at $49/month plus $6 per person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Employment Hero&lt;/strong&gt; (formerly KeyPay) is the dominant Xero payroll integration in Australia and UK, handling award rate calculations, single touch payroll compliance, and automatic Xero reconciliation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Xero Payroll&lt;/strong&gt; is Xero's native offering in select regions. Where it's available, it's built directly into the platform — no integration required.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRM
&lt;/h3&gt;

&lt;p&gt;CRM integrations connect customer financial history to the sales and relationship layer. When your CRM knows what a customer owes or has paid, your sales and account management teams have full context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HubSpot&lt;/strong&gt; integrates with Xero to sync invoice data, payment status, and customer records. Sales teams see whether an account is current or overdue before reaching out. HubSpot's free tier supports basic integration; more advanced sync requires a paid plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salesforce&lt;/strong&gt; has multiple Xero connection options via marketplace apps. Enterprise B2B companies commonly use it to keep quote-to-cash data consistent between their CRM and accounting system.&lt;/p&gt;

&lt;h3&gt;
  
  
  eCommerce
&lt;/h3&gt;

&lt;p&gt;eCommerce integrations sync sales, inventory, and payment data from your online store into Xero automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shopify&lt;/strong&gt; has a native Xero integration that posts a daily sales summary to Xero, tracks pending payments from Shopify Payments and PayPal through a clearing account, and reconciles completed transactions against the correct bank account. It's free to enable in most regions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WooCommerce&lt;/strong&gt; integrates via apps like Xero for WooCommerce, pushing orders, refunds, and payment data into Xero as they happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A2X&lt;/strong&gt; is widely used as a middleware layer for eCommerce businesses. It summarises Amazon, Shopify, Etsy, and other marketplace payouts into clean Xero journal entries by period.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reporting and analytics
&lt;/h3&gt;

&lt;p&gt;Reporting integrations extend beyond what Xero's native reports offer — particularly for multi-entity businesses, custom dashboards, and forward-looking financial modelling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fathom&lt;/strong&gt; pulls Xero data to generate KPI dashboards, visual reports, and variance analysis. Used by accounting firms to build client-facing reporting packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spotlight Reporting&lt;/strong&gt; is common in the accounting and advisory space for consolidated multi-entity reports and three-way forecasting (P&amp;amp;L, balance sheet, cash flow).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syft Analytics&lt;/strong&gt; provides real-time Xero-connected dashboards, benchmarking, and automated narrative commentary for financial statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inventory and operations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cin7&lt;/strong&gt; and &lt;strong&gt;DEAR Inventory&lt;/strong&gt; (now Cin7 Core) are the most widely used inventory management tools with Xero integration. They handle multi-location stock, purchase orders, and manufacturing BOMs, syncing financial transactions to Xero in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unleashed&lt;/strong&gt; is popular in the wholesale and distribution space. It syncs inventory valuations, sales orders, and purchase orders to Xero and handles multi-currency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time tracking and professional services
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Harvest&lt;/strong&gt; integrates with Xero to convert tracked time into invoices, which are then pushed to Xero for accounts receivable management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WorkflowMax&lt;/strong&gt; (Xero's own project management tool, now independently operated as BlueRock's WFM) provides time tracking, job management, and invoicing with a direct Xero connection.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to build a Xero integration into your product
&lt;/h2&gt;

&lt;p&gt;If you're a SaaS company or fintech adding Xero support to your product, there are a few things to understand before you start.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Xero API
&lt;/h3&gt;

&lt;p&gt;Xero uses OAuth 2.0 for authentication. Access tokens expire every 30 minutes, so you need token refresh logic built in from day one. Refresh tokens last 60 days — but once used, they expire within 30 minutes, so accurate timing is essential. Always use the most recently issued token set and discard older ones; stale tokens cause auth errors that surface as bugs for your users.&lt;/p&gt;

&lt;p&gt;For a full technical walkthrough, see our guide on &lt;a href="https://www.apideck.com/blog/xero-integration-authentication-and-accounts-receivable-best-practices" rel="noopener noreferrer"&gt;Xero integration authentication and accounts receivable best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get your API credentials, you'll need to create an app in the Xero developer portal and generate a Client ID and Client Secret. See &lt;a href="https://www.apideck.com/blog/how-to-get-xero-api-key" rel="noopener noreferrer"&gt;how to get your Xero API key&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xero App Partner program
&lt;/h3&gt;

&lt;p&gt;Xero limits unapproved apps to 25 active connections by default. To go beyond that, you need to apply to become a Xero App Partner — which involves onboarding at least 3 active customer connections within 30 days during review, and agreeing to commercial terms.&lt;/p&gt;

&lt;p&gt;In March 2026, Xero moved to a new tiered API pricing model with five tiers (Starter, Core, Plus, Advanced, Enterprise) based on active connections and data egress volume. This replaced the previous revenue-share model. For a detailed breakdown of what each tier costs and how to optimize your spend, see our &lt;a href="https://www.apideck.com/blog/xero-api-pricing-and-the-app-partner-program" rel="noopener noreferrer"&gt;Xero API pricing guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bank Feeds
&lt;/h3&gt;

&lt;p&gt;If you're building a fintech product that needs to push transactions into Xero automatically, you'll need access to Xero's Bank Feeds API. This is a closed API — it's not available to all developers by default. You need a direct partnership agreement with Xero, which involves a certification process that typically takes 15–30 months.&lt;/p&gt;

&lt;p&gt;For most fintech companies, that timeline is a dealbreaker. See our detailed breakdown of &lt;a href="https://www.apideck.com/blog/bank-feeds-api-integration" rel="noopener noreferrer"&gt;bank feeds API integration&lt;/a&gt; and what it actually takes to ship this feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  The maintenance problem
&lt;/h3&gt;

&lt;p&gt;Building a direct Xero integration is the start, not the end. Xero ships API changes. Authentication flows evolve. Data models update. Every change requires engineering time to track, assess, and patch. Over a multi-year timeline, this overhead compounds.&lt;/p&gt;

&lt;p&gt;That's before you factor in customer demand for QuickBooks, NetSuite, Sage, and other platforms. Each direct integration multiplies the maintenance surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Xero integration best practices
&lt;/h2&gt;

&lt;p&gt;Xero publishes &lt;a href="https://developer.xero.com/documentation/guides/how-to-guides/integration-best-practices/" rel="noopener noreferrer"&gt;official integration best practices&lt;/a&gt; that every developer building on the platform should internalize. These aren't just technical recommendations — with the move to usage-based pricing, they're also directly commercial. Inefficient API calls now cost real money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use webhooks instead of polling
&lt;/h3&gt;

&lt;p&gt;Polling — repeatedly asking "has anything changed?" on a timer — is the most common source of unnecessary API calls and data egress. Xero supports webhooks for key endpoints, which means Xero pushes a notification to your app when something changes. You only fetch data when there's actually something to fetch.&lt;/p&gt;

&lt;p&gt;If you genuinely can't use webhooks for a specific use case, choose longer polling intervals, support user-initiated refresh, and respect Xero's &lt;code&gt;Retry-After&lt;/code&gt; response headers with exponential backoff. Don't hammer the API on a fixed schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache aggressively
&lt;/h3&gt;

&lt;p&gt;Caching Xero responses — and storing a timestamp of when you last fetched — is often the single biggest win for cutting unnecessary API calls. If your data was fresh five minutes ago and nothing has changed, don't fetch it again. For static or slow-changing data like account codes, chart of accounts, or tax rates, cache for hours or days. Only invalidate the cache when a webhook or user action signals that something has changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use pagination and bulk retrieval correctly
&lt;/h3&gt;

&lt;p&gt;When a customer first connects to your app, you may need to pull their full history — contacts, invoices, bank transactions, items. Don't do this with repeated single-record calls. Use Xero's pagination parameters (&lt;code&gt;page=1&lt;/code&gt;, &lt;code&gt;page=2&lt;/code&gt;, etc.) and increase the default page size from 100 up to 1,000 records per request using the &lt;code&gt;pageSize&lt;/code&gt; parameter. Pull in bulk from Xero and parse at your end — it's far more efficient than trying to filter server-side across many small requests.&lt;/p&gt;

&lt;p&gt;For subsequent syncs, use the &lt;code&gt;modifiedAfter&lt;/code&gt; filter to only retrieve records that have changed since your last sync. This is one of the most impactful API efficiency improvements available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Respect rate limits and handle errors gracefully
&lt;/h3&gt;

&lt;p&gt;Xero enforces both per-tenant and app-wide rate limits. When you hit a limit, Xero returns a &lt;code&gt;429 Too Many Requests&lt;/code&gt; response along with a &lt;code&gt;Retry-After&lt;/code&gt; header telling you how long to wait. Build this handling in from the start — catch 429s, read the header, wait the specified time, and retry. Don't retry immediately. Don't retry in a tight loop.&lt;/p&gt;

&lt;p&gt;Beyond rate limits, build general error handling that logs failures with context. Silent errors that get swallowed are the hardest bugs to find later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manage connections carefully
&lt;/h3&gt;

&lt;p&gt;With usage-based pricing, every active connection to a Xero organisation counts toward your monthly bill. Audit your connections regularly. If a customer has disconnected, churned, or is on a free tier that doesn't use Xero data, remove or deactivate the connection rather than leaving it live. Xero's Developer Portal has a History (and soon, Logs) page where you can see connection usage and rate-limit hits — use it proactively to spot patterns before they become cost problems.&lt;/p&gt;

&lt;p&gt;For multi-tenant apps where multiple users in the same organisation may authenticate separately, store and reuse tokens per organisation rather than per user where possible. Avoid creating duplicate connections to the same Xero tenant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimise data egress
&lt;/h3&gt;

&lt;p&gt;Data egress — the volume of data returned by API calls — is the other variable in Xero's pricing model. Use field filtering where the API supports it to return only the fields your app actually uses. Don't pull full invoice objects when you only need the status and total. Combine this with caching and webhook-driven fetches, and your egress footprint can drop significantly without any change to the user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep data integrity tight
&lt;/h3&gt;

&lt;p&gt;Xero's data model is precise and transactional. A few rules that catch teams out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never post duplicate transactions. Use idempotency keys where supported, and check whether a record already exists before creating a new one.&lt;/li&gt;
&lt;li&gt;Reconcile regularly. If your integration writes data to Xero, build a reconciliation mechanism that compares your internal state against Xero's ledger on a schedule. Catch drift early.&lt;/li&gt;
&lt;li&gt;Handle rounding correctly. Xero applies specific rounding rules to line items and totals. Mismatched rounding is a common source of sync errors that only surface at month-end.&lt;/li&gt;
&lt;li&gt;Don't modify data in Xero that the user owns. Integrations should write what they're responsible for and leave everything else alone.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Building Xero integrations with a unified API
&lt;/h2&gt;

&lt;p&gt;The alternative to building direct is using a unified API layer. Apideck's &lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt; connects your product to Xero and 29+ other accounting platforms through a single normalized interface. You write the integration once, and it works across Xero, QuickBooks Online, NetSuite, Sage Intacct, MYOB, and more.&lt;/p&gt;

&lt;p&gt;What this means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No OAuth headaches. Apideck handles token refresh, credential storage, and connection management through a pre-built Vault component your users authorize directly.&lt;/li&gt;
&lt;li&gt;No data model wrangling. Invoices, contacts, accounts, and journals look the same regardless of which accounting platform a customer uses.&lt;/li&gt;
&lt;li&gt;No API maintenance. When Xero ships a change, Apideck absorbs it. Your code doesn't need to change.&lt;/li&gt;
&lt;li&gt;Bank feeds support without the 15–30 month partnership process. Apideck has the Xero partnership in place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick example using the Node.js SDK:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="c1"&gt;// List invoices from Xero&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Switch serviceId to 'quickbooks' or 'netsuite' — same code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a broader look at the accounting API landscape, 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 2026&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the right Xero integration
&lt;/h2&gt;

&lt;p&gt;A few questions to anchor the decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What data needs to flow?&lt;/strong&gt; Invoice sync is different from bank feeds, which is different from expense categorization. Start with the specific data objects your use case requires and check whether the integration actually supports them — not just in marketing copy, but in the API documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which direction does data need to move?&lt;/strong&gt; Some integrations are read-only (pulling Xero data for reporting). Others write back (pushing payments, bills, or journal entries). Bidirectional sync is more complex and more valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What accounting platforms do your customers actually use?&lt;/strong&gt; If 60% of your customers use Xero but 30% use QuickBooks, building only for Xero leaves a third of your customer base underserved. A unified API solves this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the ongoing maintenance costs?&lt;/strong&gt; A direct integration that ships in 6 weeks might cost 2x as much over three years once you account for maintenance. Factor this in.&lt;/p&gt;

&lt;p&gt;For a broader context on what accounting integrations are and why they matter for modern SaaS products, see &lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;accounting integrations: what they are and how to build them&lt;/a&gt;.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;What is integration with Xero?&lt;/strong&gt;&lt;br&gt;
Integration with Xero means connecting another software application to Xero so that data syncs automatically between them — without manual exports, copy-pasting, or duplicate data entry. Common examples include syncing invoices from a CRM, pushing payroll transactions from a payroll tool, and feeding bank transactions from a fintech product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the most popular Xero integration?&lt;/strong&gt;&lt;br&gt;
Stripe, Shopify, and Gusto are among the most widely used. The most popular category depends on the business type: payroll for service businesses, eCommerce connectors for online retailers, and expense management for companies with distributed spend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to build a Xero integration?&lt;/strong&gt;&lt;br&gt;
A minimal direct integration takes 4–8 weeks for a small engineering team. Production-ready, with error handling, token refresh, and webhook support, is closer to 3–6 months. Using a unified API like Apideck compresses that to days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Xero charge for API access?&lt;/strong&gt;&lt;br&gt;
As of March 2, 2026, yes. Xero moved to a tiered pricing model based on connected organizations and API usage. There is a free Starter tier suitable for MVPs and testing, with paid tiers (Core, Plus, Advanced, Enterprise) for production applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the Xero App Marketplace?&lt;/strong&gt;&lt;br&gt;
The Xero App Marketplace is Xero's directory of third-party applications that integrate with Xero. It contains over 1,000 apps across categories including payments, payroll, eCommerce, and CRM. Listing your app requires becoming a Xero App Partner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I integrate Xero with QuickBooks?&lt;/strong&gt;&lt;br&gt;
Not directly — they're competing platforms. However, if you're building a product that needs to support both, a unified API handles both through the same codebase.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Xero's integration ecosystem is one of its biggest strengths. With over 1,000 apps across every category a small or mid-sized business touches — payroll, payments, eCommerce, CRM, expenses, reporting — there's rarely a workflow that can't be automated with the right connector.&lt;/p&gt;

&lt;p&gt;For businesses evaluating Xero integrations: focus on the specific data flows your team needs, check that the integration supports bidirectional sync if you need it, and consider the ongoing support footprint before committing.&lt;/p&gt;

&lt;p&gt;For product teams building a Xero integration: the direct API route works but carries a maintenance burden that compounds over time, especially as you expand to other accounting platforms. A unified API handles the infrastructure so your engineering team can focus on the product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Get started with Apideck's Xero integration →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building products that connect to Xero alongside other accounting platforms, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Accounting API&lt;/a&gt; provides a unified integration layer for Xero, QuickBooks, NetSuite, Sage, and more — so you build once and connect to all of them through a single, normalized API.&lt;/p&gt;

</description>
      <category>api</category>
      <category>accountingintegration</category>
      <category>xerointegration</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>Agentic Payments Are Rewriting Spend Management From Scratch</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Wed, 24 Jun 2026 13:03:04 +0000</pubDate>
      <link>https://dev.to/apideck/agentic-payments-are-rewriting-spend-management-from-scratch-30i5</link>
      <guid>https://dev.to/apideck/agentic-payments-are-rewriting-spend-management-from-scratch-30i5</guid>
      <description>&lt;p&gt;AI agents can now hold virtual Visa cards, execute purchases autonomously, and settle transactions with other agents. Every major payment network is racing to build the rails. For fintech founders, this is not a feature to bolt on. It is a new category of financial infrastructure emerging in real time.&lt;/p&gt;

&lt;p&gt;The stakes are large. McKinsey estimates $3 to $5 trillion in global agentic commerce by 2030. Yet the infrastructure remains dangerously fragmented, with competing payment protocols, no interoperability, and a liability vacuum that no legal framework has fully addressed. Before getting into who is building what, it helps to understand what "agentic payments" actually means in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four layers of agent commerce
&lt;/h2&gt;

&lt;p&gt;Most of what people call agent commerce today is not truly autonomous. There are four distinct layers.&lt;/p&gt;

&lt;p&gt;Layer one: you research a product using an AI assistant and place the order yourself. Layer two: the agent finds the product and you confirm the payment. Layer three: you set a condition and the agent buys automatically when it is met. All three of these layers still use your payment credentials.&lt;/p&gt;

&lt;p&gt;Layer four is where things get genuinely different. Your agent needs to call another model's API, purchase a dataset to complete a task, or hire a sub-agent to handle a subtask. In these scenarios, the agent is not using your card. It needs its own payment credentials. The current workaround is for developers to buy access on the agent's behalf and hand over credentials. That is not agency. That is acting as a purchasing agent.&lt;/p&gt;

&lt;p&gt;Virtual cards are the first serious answer to this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agent-native virtual cards actually do
&lt;/h2&gt;

&lt;p&gt;A product like &lt;a href="https://agentcard.sh/" rel="noopener noreferrer"&gt;AgentCard&lt;/a&gt; makes the mechanics concrete. You top up a balance, the agent calls a tool like &lt;code&gt;create_card(amount=$50)&lt;/code&gt;, and the provider issues a one-time prepaid Visa card locked to exactly that amount. The card is single-use, automatically deactivated after the transaction, and the agent never sees your underlying funding source. No overdraft risk. No credential exposure.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/1AjmMhpNAL30dggAhNqAqx/4b8e3797fb855306ebb33c422d537c00/Screenshot_2026-03-12_at_07.01.21.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/1AjmMhpNAL30dggAhNqAqx/4b8e3797fb855306ebb33c422d537c00/Screenshot_2026-03-12_at_07.01.21.png" alt="Agentcard.sh"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not just a developer toy. It reflects a structural shift in how software budgets will work: per-task allocation replacing per-employee card programs.&lt;/p&gt;

&lt;p&gt;Three conditions matured simultaneously to make this possible. The demand side arrived with a new class of software builders: 25% of YC's Winter 2025 batch has over 95% of its codebase generated by AI, and 67% of Bolt.new's five million users are non-professional developers. On the supply side, APIs like Stripe Issuing made virtual card issuance programmable. And at the network layer, Visa and Mastercard both shipped agent payment protocols in 2025, with Cloudflare and Fiserv following quickly. Everyone saw the same thing: agents need their own financial infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A three-layer solution stack
&lt;/h2&gt;

&lt;p&gt;Virtual cards solve the compatibility problem but they inherit a core limitation from traditional card rails: slow settlement. When a merchant receives a card payment, funds can take days or weeks to clear. For an agent managing high-frequency micropayments across APIs and sub-agents, that lag compounds quickly. Cross-border fees add further friction.&lt;/p&gt;

&lt;p&gt;The x402 protocol, developed by Coinbase with Cloudflare, addresses this at the HTTP layer. An agent sends a request, the server responds with a 402 status code and a price, the agent signs a USDC transfer, and the data is returned. No account registration, no subscription, pay-as-you-go. The entire transaction is a single HTTP exchange. x402 has already reached $600 million in annualised volume.&lt;/p&gt;

&lt;p&gt;These two approaches serve different use cases and are not competing. Virtual cards work wherever Visa is accepted: online retail, SaaS subscriptions, cloud service billing. x402 and similar stablecoin rails work for agent-to-agent payments, API calls, and sub-agent hiring where card rails are either too slow or too expensive.&lt;/p&gt;

&lt;p&gt;The three-layer stack that is emerging looks like this. Virtual cards address compatibility with the existing merchant world. Stablecoins address settlement speed and micropayment economics. Native programmable wallets address the governance complexity that emerges when one agent is managing dozens of sub-agents, each with their own transaction history. Which layer matters most depends entirely on what your agent is transacting with today.&lt;/p&gt;

&lt;p&gt;This is directly relevant for teams building on &lt;a href="https://openbankingtracker.com/agentic-payments" rel="noopener noreferrer"&gt;open banking infrastructure&lt;/a&gt;: the same account-to-account payment rails that power open banking are candidates for the stablecoin-speed settlement layer, without requiring new network relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  The infrastructure players
&lt;/h2&gt;

&lt;p&gt;The established networks moved quickly. Mastercard launched Agent Pay in mid-2025, processing its first on-network agentic transaction and rolling out to all US cardholders by November 2025. The system uses Agentic Tokens tied to each authorised agent interaction. In March 2026, Mastercard and Santander completed Europe's first live end-to-end AI agent payment within a regulated banking framework.&lt;/p&gt;

&lt;p&gt;Visa responded with Intelligent Commerce, opening VisaNet to developers building AI commerce agents. The suite includes tokenised AI-Ready Cards and Payment Instructions loaded directly into VisaNet. Over 100 partners are in build or sandbox. By December 2025, Visa had completed hundreds of secure agent-initiated transactions in US pilots.&lt;/p&gt;

&lt;p&gt;Stripe moved fastest on open protocol. The Agentic Commerce Protocol, co-developed with OpenAI and released in September 2025, powers ChatGPT's Instant Checkout. It introduced Shared Payment Tokens: scoped, time-limited, revocable credentials that let agents initiate payments without exposing underlying card details. Affirm and Klarna both integrated buy-now-pay-later via SPTs in March 2026. Google's Agent Payments Protocol took a different approach, using cryptographically signed Mandates and W3C Verifiable Credentials to create a tamper-proof audit trail that is payment-rail agnostic.&lt;/p&gt;

&lt;p&gt;At the startup layer, Skyfire raised $9.5 million from a16z CSX and Coinbase Ventures to build agent identity and KYA (Know Your Agent) infrastructure. Natural raised $9.8 million to build B2B payment rails for agent-led &lt;a href="https://www.apideck.com/use-cases/procurement-automation" rel="noopener noreferrer"&gt;procurement workflows&lt;/a&gt;. Payman AI raised $13.8 million with participation from Visa and Coinbase Ventures, building a platform for financial institutions to deploy agent-driven transactions. Kite AI raised $35 million from PayPal and General Catalyst to build a Layer 1 blockchain specifically for agent payment infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for spend management
&lt;/h2&gt;

&lt;p&gt;Traditional spend management platforms face a two-wave disruption.&lt;/p&gt;

&lt;p&gt;Wave one is actually strengthening them in the short term. Brex launched AI-driven expense automation in fall 2025, claiming 99% of expense reports handled without human review. Ramp Agents, launched in July 2025, catches 15 times more out-of-policy spend than non-AI alternatives with 99% policy enforcement accuracy. Upload a policy document and the agent builds a reasoning graph in minutes. Vendr went a step further with Ruth Negotiations, launching AI agent contract negotiations powered by their dataset of billions in dollars of software purchases. You describe the deal and target price, Ruth analyzes the quote, builds a negotiation strategy, and runs the negotiation on your behalf, handling the tail spend that finance teams typically do not have bandwidth to optimize.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agent.vendr.com/" rel="noopener noreferrer"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/3UNIuVCEoGq1m44OnHM4hn/a7e6e528118be71f76ceb8736e8eacb2/Screenshot_2026-03-12_at_07.13.15.png" alt="Vendr AI Agent - Ruth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wave two is more disruptive. When agents transact autonomously via agent-native protocols, settling in stablecoins through programmable wallets with per-task budgets, the concept of a corporate card built around human-in-the-loop approvals becomes structurally misaligned. A card network's 30-cent fixed fee makes micropayments uneconomical. Its assumption of a human authorising each transaction breaks at machine speed. These are not product problems. They are architectural ones. The &lt;a href="https://www.apideck.com/erp-api" rel="noopener noreferrer"&gt;ERP API integrations&lt;/a&gt; that currently connect spend management platforms to ERPs will need to accommodate agent-initiated transaction records that arrive at very different frequencies and formats than human expense submissions.&lt;/p&gt;

&lt;p&gt;a16z put it plainly: an agent streaming $0.001 per second to a compute provider and a manufacturer settling a $50,000 vendor invoice can share the same rail, but only on stablecoin infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unsolved liability problem
&lt;/h2&gt;

&lt;p&gt;Every major institution converges on the same unresolved question: who is liable when an agent makes a bad purchase?&lt;/p&gt;

&lt;p&gt;The Consumer Bankers Association's January 2026 white paper, convening Bank of America, JPMorgan, PNC, Google, Stripe, Visa, and Mastercard, concluded that under the Electronic Fund Transfer Act, consumers may be liable for mistakes their agents make when they voluntarily grant an access device to an AI. Chargebacks introduce an additional party with no established allocation of responsibility.&lt;/p&gt;

&lt;p&gt;Fraud vectors are shifting in parallel. Visa has reported a significant increase in dark web activity around AI agent fraud tooling. The threat model has moved from stolen credentials to hijacked agents: malware that distorts agent behaviour so transactions technically follow preset rules but do not reflect user intent. High-frequency, small-value abuse can overwhelm fraud teams before it shows up as a visible loss.&lt;/p&gt;

&lt;p&gt;Authorization standards remain fragmented. Mastercard uses Agentic Tokens, Visa uses Trusted Agent Protocol, Google uses cryptographic Mandates. None of these interoperate. The "Know Your Agent" standard being developed mirrors KYC for machines, but no unified standard exists yet.&lt;/p&gt;

&lt;p&gt;Gartner predicts over 40% of agentic AI projects will be cancelled by end of 2027 due to escalating costs or inadequate risk controls. The EU AI Act's 2026 obligations require human oversight and auditability. PSD2's Strong Customer Authentication has no mechanism for treating AI agents as equivalent to human payers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The historical pattern and where the opportunity is
&lt;/h2&gt;

&lt;p&gt;The pattern here is consistent. eBay created a marketplace of individual sellers who could not get merchant accounts. PayPal solved their payment problem. Independent developers needed to accept online payments without an enterprise sales cycle. Stripe solved it with a few lines of code. Each platform shift produced a new class of merchants that existing rails could not serve. The winner was whoever served them first.&lt;/p&gt;

&lt;p&gt;The new merchant class is agents. Millions of builders who could not write production code two years ago are now shipping software with AI. Their agents will transact, and those transactions need infrastructure.&lt;/p&gt;

&lt;p&gt;The competitive map has a clear gap. Networks own identity and authorisation. Stripe and Google own checkout protocols. Brex and Ramp own the automation layer for human-in-the-loop workflows. What remains almost entirely unbuilt is the connective tissue: cross-protocol interoperability, real-time policy engines that work across competing standards, agent identity registries, and the audit and liability infrastructure for when things go wrong.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apideck.com/blog/what-is-a-unified-api" rel="noopener noreferrer"&gt;unified API layer&lt;/a&gt; becomes meaningful here. When an enterprise deploys agents across procurement, vendor payments, and sub-agent orchestration, the accounting system of record needs to receive all of it coherently, regardless of whether the underlying transaction settled on a Visa rail, via x402, or through a stablecoin wallet. Normalising agent-initiated transaction data across those surfaces is exactly the kind of integration problem that a unified API layer exists to solve.&lt;/p&gt;

&lt;p&gt;Whoever builds the governance layer that makes autonomous spending trustworthy, the compliance infrastructure, the audit trails, the policy engines, and the kill switches that let an enterprise hand an agent a budget and operate confidently, will define the next generation of financial infrastructure. The scaffolding is in place. The real infrastructure has barely started.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>api</category>
      <category>software</category>
    </item>
    <item>
      <title>Accounting Software Integration: Architecture, Trade-offs, and Costs for SaaS Teams</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 19 Jun 2026 13:33:23 +0000</pubDate>
      <link>https://dev.to/apideck/accounting-software-integration-architecture-trade-offs-and-costs-for-saas-teams-2h3i</link>
      <guid>https://dev.to/apideck/accounting-software-integration-architecture-trade-offs-and-costs-for-saas-teams-2h3i</guid>
      <description>&lt;p&gt;Accounting software integration is product infrastructure. For B2B SaaS, fintech, and vertical SaaS companies that handle financial data, connecting your product to external accounting systems like &lt;a href="https://www.apideck.com/integrations/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt;, &lt;a href="https://www.apideck.com/integrations/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt;, or &lt;a href="https://www.apideck.com/integrations/sage-intacct" rel="noopener noreferrer"&gt;Sage&lt;/a&gt; is an architectural decision that directly affects reliability, time to market, total cost of ownership (TCO), and customer retention.&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 trend&lt;/a&gt; is making accounting software integration a baseline expectation rather than a nice-to-have — businesses now demand that their financial tools connect seamlessly.&lt;/p&gt;

&lt;p&gt;This article covers what accounting integration involves at a technical level, the architecture options available to your team, the real costs of building in-house versus buying through a unified API, and a practical framework for evaluating providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Accounting Software Integration" Actually Means
&lt;/h2&gt;

&lt;p&gt;Accounting software integration is the programmatic connection between your product and one or more external accounting systems. The goal is bidirectional data flow: your application creates, reads, updates, and syncs financial objects (invoices, payments, journal entries, accounts, contacts, tax rates) inside your customers' accounting platforms.&lt;/p&gt;

&lt;p&gt;This goes well beyond a single API call. A production-grade integration requires OAuth flow handling and token lifecycle management across providers, mapping your internal data model to each provider's schema, managing sync patterns (real-time webhooks where available, scheduled polling where not), rate limit and retry logic on a per-provider basis, and multi-tenant credential isolation.&lt;/p&gt;

&lt;p&gt;Each accounting platform brings its own API design, authentication method, object model, and constraints. QuickBooks Online uses OAuth 2.0 with short-lived tokens. Xero requires tenant-specific headers and enforces strict rate limits. NetSuite uses token-based authentication with an entirely different data model. Multiply these differences across ten or twenty providers and the scope of the problem becomes clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters for Your Product and Customers
&lt;/h2&gt;

&lt;p&gt;Integration support directly impacts buying decisions. According to &lt;a href="https://www.smb-gr.com/channels/the-results-are-in-smb-groups-2024-technology-buying-journey-survey/" rel="noopener noreferrer"&gt;SMB Group's 2024 Technology Buying Journey survey&lt;/a&gt;, 34% of SMBs rank compatibility with existing systems as a top factor when shortlisting solutions, with that number climbing even higher for companies between 100 and 499 employees. Separately, &lt;a href="https://www.smb-gr.com/channels/the-results-are-in-smb-groups-2024-technology-buying-journey-survey/" rel="noopener noreferrer"&gt;Capterra's SMB Software Buying Trends Survey&lt;/a&gt; found that 38% of SMBs rate integration capabilities as one of the most important factors when evaluating new software. If your product can't connect to the accounting tool a prospect already uses, you lose the deal before the demo ends.&lt;/p&gt;

&lt;p&gt;The business case extends beyond deal velocity. Integrated accounting systems eliminate manual data duplication and provide real-time visibility into financial operations. Expend, for example, uses Apideck's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;accounting unified API&lt;/a&gt; to provide seamless integration with platforms like Xero, QuickBooks, and Sage, enabling automatic categorization and tracking of expenses while reducing manual entry for their customers. When your product automatically syncs invoices, payments, or expense data to a customer's general ledger, you reduce reconciliation errors, accelerate month-end close for their finance teams, and embed your product into their core financial workflow.&lt;/p&gt;

&lt;p&gt;For SaaS teams, this translates to higher retention rates, expanded deal sizes (integration is often a gating criterion for mid-market and enterprise accounts), and lower support load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Integration Scenarios
&lt;/h2&gt;

&lt;p&gt;Before evaluating architecture models, it's worth grounding the discussion in the specific workflows your product will need to support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invoice sync&lt;/strong&gt; is the most common starting point. Your product generates an invoice and needs to create or update the corresponding record in the customer's accounting system with correct line items, tax treatment, and account mapping. &lt;strong&gt;Payment reconciliation&lt;/strong&gt; follows closely: matching payments received in your platform against open invoices in the accounting system, handling partial payments, credits, and refunds.&lt;/p&gt;

&lt;p&gt;Other high-value workflows include accounts receivable sync, chart of accounts mapping (allowing customers to map your product's categories to their GL structure), expense and payout posting, and journal entry creation for complex transactions that don't fit standard invoice or payment objects.&lt;/p&gt;

&lt;p&gt;Each workflow multiplies the integration surface area. The invoice object alone varies significantly between providers with different required fields, tax calculation models, currency handling, and line-item structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Integration Architecture Models (and When They Make Sense)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Native Vendor Integrations
&lt;/h3&gt;

&lt;p&gt;Some accounting platforms offer their own integration tooling through pre-built connectors or app marketplace frameworks. The upside is speed: you can ship a single integration quickly using the vendor's SDK.&lt;/p&gt;

&lt;p&gt;The limitation is control and breadth. You're locked into one vendor's ecosystem, and the integration surface is restricted to what the vendor exposes. If your customers use a mix of accounting tools (which is almost always the case), native integrations don't scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom In-House Integrations
&lt;/h3&gt;

&lt;p&gt;Building point-to-point integrations gives your team full control over data flow, error handling, and business logic. For a single connector against a well-documented API, this can be a reasonable starting point.&lt;/p&gt;

&lt;p&gt;The cost equation changes fast when you add a second or third provider. Each new connector introduces its own authentication flow, data model, rate-limiting behavior, webhook format (if webhooks exist at all), and edge cases. Industry analysis consistently shows that a single production-grade connector requires roughly 30+ person-days of engineering effort covering scoping, implementation, testing, documentation, and deployment, with significant ongoing maintenance thereafter.&lt;/p&gt;

&lt;p&gt;The real cost driver is combinatorial complexity. Auth handling for five providers means five different OAuth implementations (or a mix of OAuth, API key, and token-based schemes). Object mapping for five providers means five different invoice schemas, five different payment models, five different error response formats. Your integration layer becomes a second product that demands its own roadmap, on-call rotation, and monitoring infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  iPaaS and Automation Platforms
&lt;/h3&gt;

&lt;p&gt;Tools like Zapier, Workato, or Tray.io are designed for workflow automation, primarily for internal operations. They work well for connecting internal tools: syncing CRM data to a spreadsheet, triggering notifications between systems.&lt;/p&gt;

&lt;p&gt;For product-embedded accounting integrations, iPaaS platforms fall short. They typically lack multi-tenant isolation, add latency through a dependency layer you can't fully control, and aren't designed for structured bidirectional data sync. They're also difficult to embed into your product's UX as a native experience.&lt;/p&gt;

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

&lt;p&gt;A unified API abstracts multiple accounting platforms behind a single normalized API layer. You integrate once against a standardized schema (invoices, payments, contacts, ledger accounts) and the provider handles per-platform translation, authentication, rate limiting, and connector maintenance.&lt;/p&gt;

&lt;p&gt;The architectural advantage is clear: one integration surface instead of many. The vendor maintains individual connectors, handles upstream API version changes, and exposes a consistent set of endpoints regardless of which accounting system the end user connects.&lt;/p&gt;

&lt;p&gt;The trade-off is that you're introducing a dependency and may lose some provider-specific functionality. Strong unified API providers mitigate this by offering passthrough or raw-field access alongside the normalized schema, so you can still reach provider-specific endpoints when needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/66w10HBRCaBPAC9SuPKt5z/0d429d477a0ecd7f66306872469011c8/Screenshot_2026-02-06_at_17.46.35.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/66w10HBRCaBPAC9SuPKt5z/0d429d477a0ecd7f66306872469011c8/Screenshot_2026-02-06_at_17.46.35.png" alt="accounting software integration 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-offs at a Glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Native Vendor&lt;/th&gt;
&lt;th&gt;Custom In-House&lt;/th&gt;
&lt;th&gt;iPaaS&lt;/th&gt;
&lt;th&gt;Unified API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to first integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-provider scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Poor&lt;/td&gt;
&lt;td&gt;Expensive&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control over logic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;High (with passthrough)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance burden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (single)&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Product embedding fit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TCO at 5+ providers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Core Technical Requirements for Reliable Integrations
&lt;/h2&gt;

&lt;p&gt;Regardless of architecture model, a production-grade accounting integration needs to handle several non-trivial concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication and consent management.&lt;/strong&gt; Every integration starts with getting the customer to authorize access to their accounting data. This means implementing OAuth 2.0 flows (with provider-specific variations), securely storing and refreshing tokens, handling revocation and re-authorization, and building the UI for customers to manage connections. With a unified API, this entire surface is handled by the provider. Your product calls a single authorization endpoint while the vendor manages per-provider OAuth complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data model normalization&lt;/strong&gt;. An invoice in QuickBooks Online is structurally different from an invoice in Xero, which differs again from NetSuite. Line item representations, tax handling, currency fields, custom fields, and required-versus-optional attributes all vary. Building in-house means creating and maintaining a mapping layer for each provider. A unified API normalizes these into a consistent schema, typically with the option to access raw provider-specific fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync patterns and reliability.&lt;/strong&gt; Some providers support webhooks for real-time change notifications; others only support polling. Your sync engine needs to handle both, along with idempotency (preventing duplicate records from the same event), deduplication, conflict resolution, and ordering guarantees. Invoice creation may need to be near-real-time, while chart-of-accounts sync can be periodic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling and observability.&lt;/strong&gt; Accounting APIs fail in provider-specific ways: rate limit errors, validation failures, temporary outages, schema changes. A robust integration layer needs structured retry logic (exponential backoff, dead-letter queues), clear error surfacing to end users, and internal observability (dashboards, alerting, structured logs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and compliance.&lt;/strong&gt; Financial data carries regulatory weight. Token storage must be encrypted, API traffic encrypted in transit, and audit trails should capture who accessed what data and when. A proxy-based architecture that passes data through without storing it reduces the compliance surface significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-tenancy&lt;/strong&gt;. In a SaaS context, every customer connection is an isolated tenant. Credential isolation, per-tenant rate limiting, and per-tenant error handling are table stakes. Easy to underestimate during initial development; painful to retrofit later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs Buy: A Total Cost of Ownership Framework
&lt;/h2&gt;

&lt;p&gt;This is where the engineering decision becomes a financial one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In-house cost reality.&lt;/strong&gt; Building a single accounting connector covering authentication, core CRUD operations, error handling, testing, and documentation typically consumes 30+ person-days of engineering time. Depending on team location and seniority, that translates to roughly $20,000–$40,000 per connector in direct engineering cost.&lt;/p&gt;

&lt;p&gt;The initial build is only the beginning. Ongoing maintenance (handling API version changes, fixing production edge cases, managing token refresh failures, updating documentation) can consume 20–30% of an engineer's time in perpetual maintenance mode. Add observability, SLA management, and support ticket triage, and a single connector's annual run-rate often exceeds the initial build cost.&lt;/p&gt;

&lt;p&gt;Multiply that across five, ten, or twenty providers and the math gets uncomfortable fast.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Year 1 TCO = (Number of connectors × Build cost per connector) + Infrastructure + Tooling

Year 2+ TCO = (Number of connectors × Annual maintenance cost) + New connector builds + Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For a team building five accounting connectors in-house, a conservative three-year estimate: $100,000–$200,000 in initial build costs, plus $75,000–$150,000 per year in ongoing maintenance, totaling $325,000–$650,000 over three years. That excludes the opportunity cost of diverting engineering resources from your core product roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unified API cost reality.&lt;/strong&gt; With a unified API provider, you build one integration and gain access to multiple accounting platforms immediately. The vendor absorbs per-connector build and maintenance costs. Your spend becomes a predictable monthly or annual fee that scales with connected consumers, not with engineering headcount.&lt;/p&gt;

&lt;p&gt;To put concrete numbers against this: &lt;a href="https://www.apideck.com/pricing" rel="noopener noreferrer"&gt;Apideck's Launch plan&lt;/a&gt; starts at $599/month for 25 consumers and includes access to 2 Unified API categories (e.g., Accounting and CRM), passthrough API support, and web   books. The Scale plan covers 100+ consumers with additional features like custom field mapping and data scopes. Annual billing reduces costs by 10%. At $599/month ($7,188/year), you get access to every accounting connector Apideck supports, with connector maintenance, authentication handling, and infrastructure included.&lt;/p&gt;

&lt;p&gt;Compare that to the in-house scenario above: five connectors built in-house run $325,000–$650,000 over three years. The same coverage through a unified API runs roughly $21,500 over three years on the Launch plan, or more on Scale/Enterprise tiers as your consumer count grows. Even at higher tiers, the cost difference is substantial, and your engineering team stays focused on your core product.&lt;/p&gt;

&lt;p&gt;The ROI inflection point is typically around the second or third connector. If you only need a single accounting integration with no plans to expand, building in-house may be the right call. Once you're supporting three or more providers, or expect to within 12–18 months, the unified API model almost always wins on TCO.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/d6o5ai4eeewt/588dBO4WXm3WBkoBHYDNDx/1656c6de7b952c1f1ccc472bc2ad4ce3/img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/d6o5ai4eeewt/588dBO4WXm3WBkoBHYDNDx/1656c6de7b952c1f1ccc472bc2ad4ce3/img_1.png" alt="accounting software integration 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Integration Provider
&lt;/h2&gt;

&lt;p&gt;When evaluating unified API providers or any third-party integration solution, here is a practical checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider coverage and data depth.&lt;/strong&gt; How many accounting platforms does the vendor support, and how deep is the coverage? Listing a connector is one thing. Supporting full CRUD operations on invoices, payments, journal entries, and contacts across that connector is another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalized schema plus raw access.&lt;/strong&gt; A strong unified API provides a clean, normalized data model for common objects and the ability to access provider-specific fields or endpoints when needed. Without raw access, you'll hit walls on edge cases the normalized model doesn't cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer experience.&lt;/strong&gt; Evaluate documentation, SDKs, sandbox environments, and API reference quality. Your engineers will work with this tooling daily. Poor developer experience creates friction that compounds over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability and observability.&lt;/strong&gt; Ask about uptime SLAs, how the vendor handles upstream provider outages, and what monitoring and logging tools are available. You need visibility into the integration layer, particularly when things break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security posture.&lt;/strong&gt; SOC 2 Type II certification is a baseline expectation. Examine how the vendor handles token storage, whether they use a proxy-based architecture (passing data through without persisting it) or a caching model, and how they address data residency requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing predictability.&lt;/strong&gt; Per-API-call pricing can be unpredictable and creates cost anxiety as you scale. Per-consumer or per-connected-account pricing tends to be more predictable and aligns with your own revenue model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roadmap velocity.&lt;/strong&gt; How fast does the vendor ship new connectors and expand endpoints on existing connectors? Ask about release cadence and how they prioritize new provider coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Implementation Guidance for Engineering Teams
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/blog/accounting-integration" rel="noopener noreferrer"&gt;Accounting integrations&lt;/a&gt; work best when rolled out in phases, whether you build or buy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: MVP with a single high-impact workflow.&lt;/strong&gt; Pick the workflow your customers ask about most (usually invoice sync or payment reconciliation) and implement it for the one or two accounting platforms that cover the largest share of your customer base. Keep scope tight: one workflow, one or two providers, basic error handling and retry logic. Build the connection UX (OAuth flow, account mapping) and start collecting production feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Expand workflows and add reconciliation.&lt;/strong&gt; Layer on additional workflows (AR sync, journal entries, expense posting) and harden the reliability layer with idempotency, deduplication logic, and structured observability. Build or integrate a mapping UI that lets customers configure how your product's entities correspond to their chart of accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Multi-provider coverage and abstraction.&lt;/strong&gt; Expand to additional accounting providers. With a unified API, this phase is mostly configuration. If you've built in-house, this is where per-connector multiplication hits and many teams realize the maintenance trajectory is unsustainable. Invest in a clean internal abstraction layer so your product code isn't coupled to any single provider's implementation details.&lt;/p&gt;

&lt;p&gt;At each phase, build diagnostic tooling for your support team: connection status, sync history, error logs, and the ability to trigger manual re-syncs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Unified APIs Shine (and Where They Don't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Unified APIs are the clear choice when&lt;/strong&gt; your customer base uses a mix of accounting platforms, you need to support three or more providers now or within the next year, the integration is embedded in your product rather than used internally, your engineering team can't dedicate multiple engineers to integration maintenance, and speed to market is a priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In-house still makes sense when&lt;/strong&gt; you have a single-provider mandate with deep niche extensions no unified API covers, your workflows are highly specialized (for example, complex multi-entity consolidation requiring direct NetSuite SuiteScript access), or regulatory requirements demand full control over every component in the data path.&lt;/p&gt;

&lt;p&gt;For most B2B SaaS, fintech, and vertical SaaS companies, the unified API path delivers better TCO, faster time to market, and lower operational overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vendor Landscape and What It Means for Your Architecture
&lt;/h2&gt;

&lt;p&gt;The unified API market for accounting integrations has matured. When evaluating vendors, focus on architectural differences that affect your product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy-based versus caching architectures.&lt;/strong&gt; Some unified APIs cache data from upstream providers and serve it from their own storage. Others operate as a real-time proxy, forwarding requests directly to the upstream provider. A proxy-based model reduces the vendor's data storage footprint, minimizes staleness, and shrinks the compliance surface because your customers' financial data passes through but isn't stored by a third party.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalized schema with passthrough access.&lt;/strong&gt; The strongest platforms provide a clean normalized API for standard operations while exposing passthrough endpoints for provider-specific functionality. This gives your team the speed of a unified interface without sacrificing access to each underlying provider's full capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-category coverage.&lt;/strong&gt; If your product touches not just accounting but also CRM, HRIS, e-commerce, or payments, a platform that covers multiple integration categories through the same architecture can simplify your stack further.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/" rel="noopener noreferrer"&gt;Apideck&lt;/a&gt;, for example, uses a proxy-based architecture with normalized schemas and raw field access across 200+ connectors spanning accounting and multiple other categories. Its consumer-based pricing model aligns cost with connected accounts rather than API call volume, providing more predictable scaling economics for SaaS teams. The platform includes embeddable UI components for connection management, sandboxed development environments, and SOC 2 Type II certification.&lt;/p&gt;

&lt;p&gt;Regardless of which vendor you evaluate, the architectural pattern matters most: a normalized integration layer that abstracts provider complexity, reduces maintenance burden, and frees your engineering team to focus on the product features that differentiate your business.&lt;/p&gt;

</description>
      <category>api</category>
      <category>apiintegration</category>
      <category>accountingintegration</category>
      <category>accountingapi</category>
    </item>
    <item>
      <title>How to Integrate with the Rillet API in 2026</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:53:54 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-with-the-rillet-api-in-2026-3cb8</link>
      <guid>https://dev.to/apideck/how-to-integrate-with-the-rillet-api-in-2026-3cb8</guid>
      <description>&lt;p&gt;Rillet has quietly become one of the more interesting accounting platforms for SaaS and fintech companies. Built on REST principles, following the OpenAPI Specification, and recently shipping an MCP server — it's a system that's clearly designed by people who have actually integrated with legacy ERPs and didn't want to recreate that pain.&lt;/p&gt;

&lt;p&gt;This guide covers everything you need to get up and running with the Rillet API: authentication, pagination, idempotency, webhooks, error handling, and the newer MCP server setup. Whether you're syncing financial data from a payment processor, building a custom integration between Rillet and your internal systems, or using an AI coding agent to accelerate development, this should give you a solid foundation.&lt;/p&gt;

&lt;p&gt;If you'd rather skip the direct integration work, &lt;a href="https://www.apideck.com/integrations/rillet" rel="noopener noreferrer"&gt;Apideck now supports Rillet&lt;/a&gt; as a connector through the &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt;. That means you can read and write Rillet data through the same API you'd use for QuickBooks, Xero, NetSuite, or any of 30+ other accounting platforms. More on that at the end of this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Rillet API Actually Is
&lt;/h2&gt;

&lt;p&gt;Before jumping into code, it's worth understanding the scope. The Rillet API is a REST API that gives you programmatic access to everything a user can do in the UI: customers, invoices, payments, credit memos, journal entries, chart of accounts, reports (balance sheet, trial balance), and more. It's versioned, paginated, and follows RFC 9457 for structured error responses — which is a good sign.&lt;/p&gt;

&lt;p&gt;The API spec is available as an OpenAPI download at &lt;code&gt;https://docs.api.rillet.com/openapi&lt;/code&gt;, which means you can generate client code in any language you prefer without hand-rolling everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environments
&lt;/h2&gt;

&lt;p&gt;Rillet provides two environments:&lt;/p&gt;

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

&lt;p&gt;Start in sandbox. The sandbox mirrors production behavior, so anything you build there will work in production without surprises. All examples in this guide use the sandbox URL.&lt;/p&gt;




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

&lt;p&gt;Rillet uses API key authentication. To get a key, contact your Rillet team to enable API access, then create and manage keys in your Organization Settings under API Access.&lt;/p&gt;

&lt;p&gt;Every request needs the key in the &lt;code&gt;Authorization&lt;/code&gt; header as a Bearer token:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it. No OAuth dance, no token expiry to manage. Keep your key in an environment variable and never hardcode it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RILLET_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RILLET_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;RILLET_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  API Versioning
&lt;/h2&gt;

&lt;p&gt;The API is versioned, and you should always target a specific version explicitly rather than relying on the default (which falls back to v1.0). Pass the version in an HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://sandbox.api.rillet.com/customers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'X-Rillet-API-Version: 3'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of writing, v3 is the current version. Pinning your version header means you won't get unexpectedly broken by a major API update. Check the docs for the exact format — the changelog indicates versions are referenced as integers (&lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;), not decimals.&lt;/p&gt;




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

&lt;p&gt;All list endpoints use keyset-based pagination, which is the right choice for financial data where consistent page sizes and response times matter more than offset-based approaches.&lt;/p&gt;

&lt;p&gt;Responses come back in reverse chronological order and include a &lt;code&gt;pagination&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pagination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"next_cursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VdW1ptsZbOB4E1fq"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fetch the next page, pass the cursor as a query parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://sandbox.api.rillet.com/invoices?cursor=VdW1ptsZbOB4E1fq"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursors are valid for &lt;strong&gt;2 hours&lt;/strong&gt;. If yours expires, a new pagination sequence starts from the first page.&lt;/li&gt;
&lt;li&gt;The default page size is 25. You can set it with the &lt;code&gt;limit&lt;/code&gt; parameter, up to a maximum of 100.&lt;/li&gt;
&lt;li&gt;When there's no &lt;code&gt;next_cursor&lt;/code&gt; in the response, you've reached the last page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a simple function to paginate through all invoices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAllInvoices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://sandbox.api.rillet.com/invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cursor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;next_cursor&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Incremental Sync with &lt;code&gt;updated_at&lt;/code&gt; Filters
&lt;/h2&gt;

&lt;p&gt;Rillet has been rolling out &lt;code&gt;updated_at&lt;/code&gt; timestamps and &lt;code&gt;updated.gt&lt;/code&gt; filter support across its entities. As of February 2026, accounts and custom fields explicitly gained both the timestamp field and the filter parameter. Other entities — customers, invoices, payments, credit memos, and journal entries — already carried &lt;code&gt;updated_at&lt;/code&gt; in responses, though filter support varies by entity; check the changelog and OpenAPI spec for what's available on each.&lt;/p&gt;

&lt;p&gt;For accounts specifically, confirmed as of February 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://sandbox.api.rillet.com/accounts?updated.gt=2026-02-01T00:00:00Z"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For reporting journal entries, the endpoint also supports &lt;code&gt;updated.gt&lt;/code&gt; filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://sandbox.api.rillet.com/reports/journal-entries?updated.gt=2026-02-01T00:00:00Z"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general pattern — poll periodically, store the last sync timestamp, fetch only what's changed — is the right approach for building a sync pipeline. Before assuming &lt;code&gt;updated.gt&lt;/code&gt; works on a given entity, verify it against the current OpenAPI spec at &lt;code&gt;https://docs.api.rillet.com/openapi&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotency
&lt;/h2&gt;

&lt;p&gt;For POST requests (creating invoices, customers, journal entries, etc.), Rillet supports idempotency keys. This is critical if you're dealing with network failures or retries — you don't want to double-create an invoice because a request timed out.&lt;/p&gt;

&lt;p&gt;Pass a unique key in the &lt;code&gt;Idempotency-Key&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://sandbox.api.rillet.com/invoices &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer YOUR_API_KEY'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Idempotency-Key: f0e9a51e-905d-4caf-a5dc-64d326574646'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"customer_id": "cust_123", ...}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use UUID v4 for your keys — high entropy, low collision risk.&lt;/li&gt;
&lt;li&gt;The same response is returned for 24 hours after the first successful request. After that, the same key will create a new object.&lt;/li&gt;
&lt;li&gt;If a request fails due to a validation error, the response isn't saved and you can safely retry with the same key.&lt;/li&gt;
&lt;li&gt;If a second request arrives while the first is still processing, you'll get a &lt;code&gt;409 Conflict&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Rate Limiting
&lt;/h2&gt;

&lt;p&gt;The limit is 60 requests per rolling minute. Requests over that threshold return HTTP &lt;code&gt;429&lt;/code&gt;. Build in retry logic with exponential backoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rilletRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1s, 2s, 4s&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rate limit exceeded after retries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 60 requests per minute, you're unlikely to hit this in normal operations unless you're running a bulk sync. If you are, batch your calls and space them out accordingly.&lt;/p&gt;




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

&lt;p&gt;Rillet follows RFC 9457 for structured error responses. A &lt;code&gt;4xx&lt;/code&gt; error looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://rillet.com/illegal-argument"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bad Request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The start date (2026-01-01) must not be after the end date (2025-12-31). Please review the contract item."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;detail&lt;/code&gt; field is human-readable and actually useful — it tells you what went wrong. Parse the &lt;code&gt;type&lt;/code&gt; field for programmatic error handling, and surface &lt;code&gt;detail&lt;/code&gt; in your logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRilletResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rillet API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Monetary Values
&lt;/h2&gt;

&lt;p&gt;Financial amounts are returned as strings, not floats — which is the correct approach for avoiding floating point precision issues. Each amount comes with a currency code:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;currency&lt;/code&gt; field is always an ISO-4217 three-letter code. Note that Rillet allows more decimal places than the ISO standard for certain fields like unit prices, so don't assume two decimal places — parse flexibly.&lt;/p&gt;

&lt;p&gt;Use a decimal library (like &lt;code&gt;decimal.js&lt;/code&gt; or Python's &lt;code&gt;decimal&lt;/code&gt; module) rather than JavaScript's native &lt;code&gt;Number&lt;/code&gt; when doing arithmetic on these values.&lt;/p&gt;




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

&lt;p&gt;Rather than polling for changes, you can subscribe to real-time events via webhooks. Rillet supports events across entities like &lt;code&gt;Invoice&lt;/code&gt;, &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Payment&lt;/code&gt;, and &lt;code&gt;Credit Memo&lt;/code&gt;, with event types like &lt;code&gt;CREATED&lt;/code&gt; and &lt;code&gt;UPDATED&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Go to Organization Settings &amp;gt; Webhooks in the Rillet dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give it a name.&lt;/li&gt;
&lt;li&gt;Provide a publicly accessible HTTPS URL.&lt;/li&gt;
&lt;li&gt;Select the events you want to receive.&lt;/li&gt;
&lt;li&gt;Enable the webhook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An organization can have up to 5 webhooks. The recommendation from Rillet's docs is to use a single endpoint and handle routing in your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incoming Request Structure
&lt;/h3&gt;

&lt;p&gt;Every webhook request is a POST with a JSON body and these headers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Rillet-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HMAC-SHA256 signature (Base64-encoded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Rillet-Timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ISO 8601 timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Rillet-Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unique UUID for this delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Rillet-Entity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Entity type (e.g., &lt;code&gt;INVOICE&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Rillet-Event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Event type (e.g., &lt;code&gt;CREATED&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your endpoint must respond with a &lt;code&gt;2xx&lt;/code&gt; status within 30 seconds. Rillet retries failed deliveries in groups of 3 with exponential backoff, up to 5 groups before marking the webhook as failed.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;X-Rillet-Id&lt;/code&gt; for idempotency on your side — deduplicate incoming events by this ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying Signatures
&lt;/h3&gt;

&lt;p&gt;Always verify the signature. The signature is an HMAC-SHA256 hash of a concatenated payload, Base64-encoded. The signed payload format is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{timestamp}.{id}.{entity}.{event}.{raw_body}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a TypeScript implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:buffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyRilletWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-Signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-Id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-Entity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-Event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing or invalid Rillet webhook headers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signedPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;receivedBuf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedBuf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;receivedBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;expectedBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;receivedBuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedBuf&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid webhook signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Python equivalent:&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;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_rillet_webhook&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;raw_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;signatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;s&lt;/span&gt; &lt;span class="ow"&gt;in&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-Rillet-Signature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&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-Rillet-Timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;event_id&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-Rillet-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;entity&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-Rillet-Entity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;event&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-Rillet-Event&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt; &lt;span class="ow"&gt;or&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;signatures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Missing or invalid Rillet webhook headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;signed_payload&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;timestamp&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;event_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;entity&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;event&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;raw_body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;token_bytes&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;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&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;sig&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed_payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;received&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;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&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;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Signature verified.&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="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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid webhook signature&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;One edge case worth knowing: the signature header can contain up to 10 comma-separated signatures to support token rotation without downtime. Your code should verify any of them, not just the first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using the Rillet MCP Server
&lt;/h2&gt;

&lt;p&gt;This is the part that's new in 2026 and genuinely useful if you're using AI coding agents to build or maintain your integration.&lt;/p&gt;

&lt;p&gt;Rillet ships a remote MCP server at &lt;code&gt;https://docs.api.rillet.com/mcp&lt;/code&gt;. You can connect AI tools like Claude Code, Cursor, Windsurf, or Claude Desktop to it, and they'll have direct access to your Rillet account data and documentation. This means your AI assistant can look up your actual customers, check for overdue invoices, or generate integration code that reflects your real chart of accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up with Claude Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Sandbox&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http rillet-mcp-sandbox &lt;span class="se"&gt;\&lt;/span&gt;
  https://docs.api.rillet.com/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_SANDBOX_KEY"&lt;/span&gt;

&lt;span class="c"&gt;# Production&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http rillet-mcp-prod &lt;span class="se"&gt;\&lt;/span&gt;
  https://docs.api.rillet.com/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_PROD_KEY"&lt;/span&gt;

&lt;span class="c"&gt;# Verify&lt;/span&gt;
claude mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompting your AI agent, specify which environment to use: "Use the Rillet MCP in sandbox." From there you can ask questions like "What customers do I have?" or "Do I have any overdue invoices?" and get answers grounded in your actual data.&lt;/p&gt;

&lt;p&gt;This is particularly useful during development — instead of manually reading through API docs and constructing test requests by hand, you can have an AI agent explore the API surface against your sandbox data while you're building.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pulling Financial Reports
&lt;/h2&gt;

&lt;p&gt;Rillet has been adding financial report endpoints through early 2026. The current set includes:&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;# Balance sheet&lt;/span&gt;
GET /reports/balance-sheet

&lt;span class="c"&gt;# Trial balance&lt;/span&gt;
GET /reports/trial-balance

&lt;span class="c"&gt;# Income statement&lt;/span&gt;
GET /reports/income-statement

&lt;span class="c"&gt;# Cash flow statement&lt;/span&gt;
GET /reports/cash-flow-statement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four support &lt;code&gt;subsidiary_id&lt;/code&gt; and &lt;code&gt;breakdown_by&lt;/code&gt; parameters for multi-entity setups. The trial balance returns beginning balance, debits, credits, and ending balance per account. The income statement and cash flow statement both accept a date range. There's also a reporting journal entries endpoint (&lt;code&gt;GET /reports/journal-entries&lt;/code&gt;) that returns amounts converted to the subsidiary's reporting currency — useful for multi-currency consolidations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Unified API Alternative: Apideck's Rillet Connector
&lt;/h2&gt;

&lt;p&gt;If you're building a product that needs to support multiple accounting platforms and Rillet is one of them, there's a faster path than building a direct integration from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apideck.com/integrations/rillet" rel="noopener noreferrer"&gt;Apideck&lt;/a&gt; now supports Rillet as a connector through the &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt;. You can use a single API to read and write data across Rillet, &lt;a href="https://dev.to/blog/quickbooks-integration-guide"&gt;QuickBooks&lt;/a&gt;, &lt;a href="https://dev.to/blog/xero-api-integration-guide"&gt;Xero&lt;/a&gt;, &lt;a href="https://dev.to/blog/netsuite-integration-guide"&gt;NetSuite&lt;/a&gt;, &lt;a href="https://dev.to/blog/sage-api-integration-guide"&gt;Sage&lt;/a&gt;, and 30+ other accounting systems.&lt;/p&gt;

&lt;p&gt;The trade-off is the same as any abstraction layer: you get breadth and speed at the cost of some depth. The Unified API covers the core accounting objects (invoices, payments, customers, journal entries, chart of accounts) that most integrations need. If your product requires Rillet-specific features like the financial report endpoints or the MCP server, you'll still want the direct integration described above.&lt;/p&gt;

&lt;p&gt;For many use cases, starting with Apideck's connector and adding direct Rillet API calls where you need deeper functionality is the pragmatic move. Build once, connect to every accounting platform your customers use, and go deeper on Rillet where it matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Minimal Integration Example
&lt;/h2&gt;

&lt;p&gt;To tie this all together, here's a minimal Node.js integration that fetches invoices from Rillet:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.rillet.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RILLET_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Rillet-API-Version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncInvoices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoices`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cursor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({}));&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rillet error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;next_cursor&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncInvoices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Fetched &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; invoices`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Rillet API is well-designed for a relatively new platform. A few things stand out: the OpenAPI-first approach means you can generate typed clients, &lt;code&gt;updated_at&lt;/code&gt; timestamps across most entities make incremental sync viable (check entity-by-entity which ones support &lt;code&gt;updated.gt&lt;/code&gt; filtering), and the MCP server is a genuinely useful addition for teams using AI coding tools.&lt;/p&gt;

&lt;p&gt;The main things to get right from the start are version pinning, idempotency keys on write operations, webhook signature verification, and using a decimal library for monetary arithmetic. Get those four right and the rest is just building out the specific data flows your product needs.&lt;/p&gt;

&lt;p&gt;If you need to support Rillet alongside other accounting platforms, &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Apideck's Unified Accounting API&lt;/a&gt; covers the core objects through a single integration. For Rillet-specific depth, the direct API is the way to go.&lt;/p&gt;

&lt;p&gt;For the full API reference, see &lt;a href="https://docs.api.rillet.com" rel="noopener noreferrer"&gt;docs.api.rillet.com&lt;/a&gt; and the &lt;a href="https://docs.api.rillet.com/changelog" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; for what's new.&lt;/p&gt;

</description>
      <category>api</category>
      <category>accountingapi</category>
      <category>rilletapi</category>
      <category>unifiedapi</category>
    </item>
    <item>
      <title>How to Integrate Your App with QuickBooks Online: A Complete Guide for 2026</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Thu, 18 Jun 2026 13:45:59 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-your-app-with-quickbooks-online-a-complete-guide-for-2026-27b9</link>
      <guid>https://dev.to/apideck/how-to-integrate-your-app-with-quickbooks-online-a-complete-guide-for-2026-27b9</guid>
      <description>&lt;p&gt;You've decided to integrate QuickBooks Online into your app. Good call — it's the accounting platform your customers actually use. Now comes the part nobody warns you about: there's a big difference between getting it working in a sandbox and shipping something real.&lt;/p&gt;

&lt;p&gt;This guide covers the full path from initial setup to production, including the parts Intuit's documentation glosses over: getting production OAuth approval, keeping tokens alive, syncing data reliably, and what the Intuit App Partner Program actually means for your costs.&lt;/p&gt;

&lt;p&gt;If you want the technical deep-dive on specific APIs, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/how-to-get-your-quickbooks-api-key" rel="noopener noreferrer"&gt;How to get your QuickBooks API Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/exploring-the-quickbooks-online-accounting-api" rel="noopener noreferrer"&gt;Exploring the QuickBooks Online Accounting API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/quickbooks-invoice-api-integration" rel="noopener noreferrer"&gt;How to integrate the QuickBooks Invoice API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/quickbooks-api-pricing-and-the-intuit-app-partner-program" rel="noopener noreferrer"&gt;QuickBooks API Pricing and the Intuit App Partner Program&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is about architecture decisions, production readiness, and what the integration actually costs you to build and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Building
&lt;/h2&gt;

&lt;p&gt;Before writing a line of code, nail down which type of integration you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push-only integrations&lt;/strong&gt; write data into QuickBooks from your system — creating invoices when deals close, syncing payments from your payment processor, creating expense records from employee submissions. These are mostly POST operations, which are free under Intuit's current pricing regardless of volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull integrations&lt;/strong&gt; read data from QuickBooks — syncing the chart of accounts, pulling transaction history, fetching reconciliation data. These are GET operations (CorePlus API calls), which are metered starting at 500,000 calls/month on the free tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bidirectional sync&lt;/strong&gt; does both. This is the most common real-world requirement and the most complex to implement correctly, especially around conflict resolution when both systems can modify the same record.&lt;/p&gt;

&lt;p&gt;Knowing which type you need determines your architecture, your costs under the Intuit App Partner Program, and how much operational complexity you're signing up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OAuth 2.0 Flow
&lt;/h2&gt;

&lt;p&gt;QuickBooks uses OAuth 2.0 with a few quirks worth knowing before you build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up
&lt;/h3&gt;

&lt;p&gt;Create your app at &lt;a href="https://developer.intuit.com" rel="noopener noreferrer"&gt;developer.intuit.com&lt;/a&gt;. You get two sets of credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development&lt;/strong&gt;: Works with sandbox companies only. No approval needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production&lt;/strong&gt;: Works with real QuickBooks accounts. Requires app assessment (more on this later).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set your redirect URIs in the app settings. In development, &lt;code&gt;http://localhost&lt;/code&gt; is fine. In production, you need HTTPS — no exceptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Authorization Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1: Redirect user to QuickBooks consent page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://appcenter.intuit.com/connect/oauth2?
  client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;amp;response_type=code
  &amp;amp;scope=com.intuit.quickbooks.accounting
  &amp;amp;redirect_uri=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;amp;state=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;generateSecureState&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Handle the callback&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate state to prevent CSRF&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nf"&gt;getStoredState&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;State mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exchangeCodeForTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: Exchange code for tokens&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exchangeCodeForTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;realmId&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Lifecycle Management
&lt;/h3&gt;

&lt;p&gt;This is where most integrations break in production. Access tokens expire after 60 minutes. Refresh tokens for the &lt;code&gt;com.intuit.quickbooks.accounting&lt;/code&gt; scope have a maximum validity of 5 years (as of Intuit's updated November 2025 policy — previously they were rolling with no hard expiry as long as used every 100 days).&lt;/p&gt;

&lt;p&gt;What this means practically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build proactive token refresh (refresh before expiry, not after a 401)&lt;/li&gt;
&lt;li&gt;Tokens will eventually reach their 5-year hard expiry — build a reconnection flow for when that happens&lt;/li&gt;
&lt;li&gt;Store the &lt;code&gt;refresh_token_expiry&lt;/code&gt; value returned in the token response to know when a user will need to reauthorize
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getValidAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getStoredTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Refresh if within 5 minutes of expiry&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;refreshAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refreshAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markConnectionExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TOKEN_EXPIRED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;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;await&lt;/span&gt; &lt;span class="nf"&gt;saveTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Store tokens in a database, not in memory or session storage. Index by &lt;code&gt;realmId&lt;/code&gt; (the QuickBooks company ID). One user can have multiple companies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making API Calls
&lt;/h2&gt;

&lt;p&gt;The base URL differs between sandbox and production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://quickbooks.api.intuit.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://sandbox-quickbooks.api.intuit.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getValidAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QBOError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always include &lt;code&gt;minorversion=75&lt;/code&gt; in your requests. This pins you to a specific API version so Intuit's schema changes don't silently break your integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Sync Patterns
&lt;/h2&gt;

&lt;p&gt;The hardest part of a QBO integration isn't the API calls — it's keeping data consistent between two systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Sync
&lt;/h3&gt;

&lt;p&gt;For a new connection, you need to pull all historical data. QuickBooks uses a SQL-like query language with pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncAllInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;startPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Max allowed&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hasMore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`/query?query=SELECT * FROM Invoice STARTPOSITION &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startPosition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MAXRESULTS &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QueryResponse&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;upsertInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;hasMore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;startPosition&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For large accounts, this can take a while. Run it as a background job, not in the request lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incremental Sync
&lt;/h3&gt;

&lt;p&gt;After the initial sync, only pull records modified since your last sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncRecentInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastSyncTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastSyncTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`/query?query=SELECT * FROM Invoice WHERE MetaData.LastUpdatedTime &amp;gt;= '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;since&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QueryResponse&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the last successful sync time per-company in your database. If a sync fails partway through, rerun from the last checkpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conflict Resolution with SyncToken
&lt;/h3&gt;

&lt;p&gt;QuickBooks uses &lt;code&gt;SyncToken&lt;/code&gt; for optimistic locking. Any update must include the current &lt;code&gt;SyncToken&lt;/code&gt; or QuickBooks rejects it with error code &lt;code&gt;5010&lt;/code&gt; (stale object):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Fetch current version to get SyncToken&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/invoice/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SyncToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyncToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sparse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If you get &lt;code&gt;5010&lt;/code&gt;, refetch and retry.&lt;/p&gt;

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

&lt;p&gt;Polling for changes works but burns through your CorePlus API quota. Use webhooks for real-time updates.&lt;/p&gt;

&lt;p&gt;In the Intuit Developer Portal, go to your app → Webhooks and register your endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;intuit-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Respond immediately — Intuit disables slow endpoints&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Process async&lt;/span&gt;
  &lt;span class="nf"&gt;processWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_VERIFIER_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;computed&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventNotifications&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dataChangeEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;dataChangeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;queueSyncJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two non-negotiables: verify the signature (Intuit will disable your webhooks if you skip this), and respond with 200 before doing any processing.&lt;/p&gt;

&lt;p&gt;Webhooks tell you what changed, not the new value. You still need to fetch the updated record via API. They can also arrive out of order or be delayed by hours during incidents. Design sync logic to be idempotent, and run a periodic poll as a fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to Production
&lt;/h2&gt;

&lt;p&gt;Moving from sandbox to production requires Intuit's approval — this is where teams get surprised.&lt;/p&gt;

&lt;h3&gt;
  
  
  The App Assessment
&lt;/h3&gt;

&lt;p&gt;Before you can use production credentials, you need to complete Intuit's app assessment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security questionnaire&lt;/strong&gt;: How you store tokens, your data retention policy, whether you're SOC 2 compliant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case review&lt;/strong&gt;: What your app does, which APIs you use, who your users are&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo&lt;/strong&gt;: For some apps, Intuit wants to see it working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terms of Service&lt;/strong&gt;: Developer TOS plus the Intuit App Partner Program terms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Budget 1-3 weeks. Fill in the questionnaire thoroughly and you'll get through it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Intuit App Partner Program
&lt;/h3&gt;

&lt;p&gt;Once approved, your tier determines API costs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Monthly Fee&lt;/th&gt;
&lt;th&gt;CorePlus Calls Included&lt;/th&gt;
&lt;th&gt;Overage Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Builder&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;500,000&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silver&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;$3.50/1k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold&lt;/td&gt;
&lt;td&gt;$1,700&lt;/td&gt;
&lt;td&gt;Higher allocation&lt;/td&gt;
&lt;td&gt;Better rates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platinum&lt;/td&gt;
&lt;td&gt;$4,500&lt;/td&gt;
&lt;td&gt;Up to 75M&lt;/td&gt;
&lt;td&gt;$0.25/1k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Core (write) API calls are free at every tier. For most apps, Builder is fine at launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing on the Intuit App Store
&lt;/h3&gt;

&lt;p&gt;Optional but worth it — the App Store is a real distribution channel. Customers searching for integrations inside QuickBooks can find your app.&lt;/p&gt;

&lt;p&gt;To list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get production access (app assessment must be complete)&lt;/li&gt;
&lt;li&gt;Build a landing page with a clear description, privacy policy, and support contact&lt;/li&gt;
&lt;li&gt;Submit for App Store review in the developer portal&lt;/li&gt;
&lt;li&gt;Pass a UX review: Intuit checks that your OAuth flow is clean&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Being listed also signals legitimacy to enterprise buyers doing procurement reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Apideck Alternative
&lt;/h2&gt;

&lt;p&gt;Everything above is the native path. It works, but it takes weeks to build and requires ongoing maintenance — complexity that scales with each additional accounting platform your customers use.&lt;/p&gt;

&lt;p&gt;Apideck's &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt; handles OAuth, token management, data normalization, and rate limit handling for QuickBooks and 30+ other platforms through a single integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native vs Apideck
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Native QuickBooks:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You're managing:&lt;/span&gt;
&lt;span class="c1"&gt;// OAuth 2.0 + CSRF, token storage + refresh (60-min access token expiry,&lt;/span&gt;
&lt;span class="c1"&gt;// 5-year refresh token maximum), reconnection flows, sandbox vs prod routing,&lt;/span&gt;
&lt;span class="c1"&gt;// QBO data model (SyncToken, DetailType, ItemRef nesting), CorePlus quota&lt;/span&gt;
&lt;span class="c1"&gt;// tracking, platform-specific error codes, webhook signature verification,&lt;/span&gt;
&lt;span class="c1"&gt;// pagination, minorversion pinning, app assessment + App Store listing...&lt;/span&gt;
&lt;span class="c1"&gt;// ...multiplied by every additional accounting platform you add later&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;qboRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Apideck:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="c1"&gt;// Same code works for QuickBooks, Xero, NetSuite, Sage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// swap to 'xero' or 'netsuite' anytime&lt;/span&gt;
  &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apideck's &lt;a href="https://www.apideck.com/products/vault" rel="noopener noreferrer"&gt;Vault&lt;/a&gt; handles the OAuth flow. You embed a pre-built UI component, users connect their QuickBooks account, and Apideck manages credentials from there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build native if&lt;/strong&gt;: QuickBooks is the only platform you'll ever need, you have deep customization requirements, or you have the bandwidth to build and maintain it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Apideck if&lt;/strong&gt;: You'll need more than one accounting integration, you want to ship in days rather than weeks, or you'd rather not manage OAuth infrastructure.&lt;/p&gt;

&lt;p&gt;For most B2B SaaS companies, QuickBooks is first but not last. The second integration is when teams switch to a unified API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Production Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Token expiry during long-running jobs&lt;/strong&gt;: If a background sync exceeds 60 minutes, your access token expires mid-job. Refresh proactively before starting any long operation, and handle token refresh inside your sync loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple company files&lt;/strong&gt;: One QuickBooks user can have multiple companies. Don't assume one user = one &lt;code&gt;realmId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandbox vs production data divergence&lt;/strong&gt;: Sandbox data resets periodically. Your integration needs to handle IDs that exist in sandbox but not production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limits at scale&lt;/strong&gt;: 500 requests per minute per company seems generous until you're doing an initial sync for a customer with a decade of transaction history. Implement request queuing and respect &lt;code&gt;Retry-After&lt;/code&gt; headers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhook reliability&lt;/strong&gt;: Intuit webhooks can be delayed by hours during incidents. Don't rely on them as your only sync mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;If you're going native:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create your developer account at &lt;a href="https://developer.intuit.com" rel="noopener noreferrer"&gt;developer.intuit.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set up a sandbox company and test your OAuth flow&lt;/li&gt;
&lt;li&gt;Build and test sync logic against sandbox data&lt;/li&gt;
&lt;li&gt;Complete the app assessment to get production credentials&lt;/li&gt;
&lt;li&gt;Submit for App Store listing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're using Apideck:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.apideck.com/signup" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; and get your API key&lt;/li&gt;
&lt;li&gt;Embed Vault for OAuth&lt;/li&gt;
&lt;li&gt;Start calling the &lt;a href="https://www.apideck.com/accounting-api" rel="noopener noreferrer"&gt;Unified Accounting API&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The investment is real either way. So is the payoff — QuickBooks integration is a checklist item on a lot of procurement reviews, and having it is often the difference between winning and losing deals in the verticals where QuickBooks dominates.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/how-to-get-your-quickbooks-api-key" rel="noopener noreferrer"&gt;How to get your QuickBooks API Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/exploring-the-quickbooks-online-accounting-api" rel="noopener noreferrer"&gt;Exploring the QuickBooks Online Accounting API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/quickbooks-invoice-api-integration" rel="noopener noreferrer"&gt;How to integrate the QuickBooks Invoice API in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/quickbooks-api-pricing-and-the-intuit-app-partner-program" rel="noopener noreferrer"&gt;QuickBooks API Pricing and the Intuit App Partner Program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/build-an-integration-with-quickbooks-desktop-in-2025" rel="noopener noreferrer"&gt;How to Build an Integration with the QuickBooks Desktop API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com//blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate With&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>unifiedapi</category>
      <category>quickbooks</category>
      <category>accountingintegration</category>
    </item>
    <item>
      <title>How to integrate the QuickBooks Invoice API in 2026</title>
      <dc:creator>Kate Apideck</dc:creator>
      <pubDate>Mon, 25 May 2026 22:15:04 +0000</pubDate>
      <link>https://dev.to/apideck/how-to-integrate-the-quickbooks-invoice-api-in-2026-1h8l</link>
      <guid>https://dev.to/apideck/how-to-integrate-the-quickbooks-invoice-api-in-2026-1h8l</guid>
      <description>&lt;p&gt;Building invoicing into your app sounds straightforward—until you open the QuickBooks API docs.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// Customer ID&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Consulting Services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Strategy consulting - 3 hours&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-02-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Development services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;50.00&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Design services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-02-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PrivateNote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal note: Rush project&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoice with Discount and Tax
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceWithDiscountAndTax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;TaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TAX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DiscountLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DiscountLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PercentBased&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DiscountPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DiscountAccountRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;86&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;TxnTaxDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;TxnTaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;74.25&lt;/span&gt; &lt;span class="c1"&gt;// (1000 - 100) * 8.25%&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading and Querying Invoices
&lt;/h2&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

&lt;p&gt;&lt;strong&gt;Not supported:&lt;/strong&gt; OR, NOT, JOIN, GROUP BY, projections (selecting specific fields)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Unpaid invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM Invoice WHERE Balance &amp;gt; '0' ORDER BY DueDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Customer's invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice WHERE CustomerRef = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Overdue invoices&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Invoice WHERE DueDate &amp;lt; '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' AND Balance &amp;gt; '0'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

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

&lt;/div&gt;



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

&lt;p&gt;QuickBooks uses &lt;strong&gt;sparse updates&lt;/strong&gt;—send only changed fields plus &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;SyncToken&lt;/code&gt;. Updates use the same endpoint as create; the presence of &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;SyncToken&lt;/code&gt; signals an update operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SyncToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyncToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Required for optimistic locking&lt;/span&gt;
    &lt;span class="na"&gt;sparse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-03-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/send?sendTo=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/send`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/octet-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To automatically email an invoice when creating it, set &lt;code&gt;EmailStatus&lt;/code&gt; to &lt;code&gt;NeedToSend&lt;/code&gt;. Note that &lt;code&gt;BillEmail&lt;/code&gt; is required when using this option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;BillEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Required when EmailStatus is NeedToSend&lt;/span&gt;
  &lt;span class="na"&gt;EmailStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NeedToSend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Options: NotSet, NeedToSend, EmailSent&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Void an Invoice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;voidInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?operation=void`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SyncToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyncToken&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

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

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

&lt;p&gt;To record a payment against an invoice, create a Payment object that references the invoice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;QuickBooks supports partial payments—just set the &lt;code&gt;Amount&lt;/code&gt; to less than the invoice total:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordPartialPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;partialAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;p&gt;A single payment can be applied across multiple invoices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Apply Credits to Invoices
&lt;/h3&gt;

&lt;p&gt;If a customer has credits (from overpayments or credit memos), you can apply them to invoices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applyCredit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;creditMemoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Zero when applying credits only&lt;/span&gt;
    &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LinkedTxn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;TxnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creditMemoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TxnType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CreditMemo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/payment?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Check if an invoice has been paid by querying its &lt;code&gt;Balance&lt;/code&gt; field or by finding linked payments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find all payments for an invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM Payment WHERE Line.LinkedTxn.TxnId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Or check invoice balance directly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPaid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPartiallyPaid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
                        &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Apideck normalizes payment recording across all accounting platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Record a payment&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transaction_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;allocations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.00&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// List payments for a customer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Before &amp;amp; After: Native QuickBooks vs. Apideck
&lt;/h2&gt;

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

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



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

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuickBooksInvoiceService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QuickBooksAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Handle authentication&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRealmId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

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

    &lt;span class="c1"&gt;// 3. Handle tax if applicable&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TxnTaxDetail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TxnTaxCodeRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxCodeId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;TotalTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculateTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Make the API call&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Handle QuickBooks-specific errors&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qbError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fault&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qbError&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6140&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Duplicate document number: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6240&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Duplicate name exists (customer/vendor/employee)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;610&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Customer not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid item reference in line items`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5010&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stale data - invoice was modified. Refetch and retry.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`QuickBooks error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;qbError&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Send email if requested (already handled via EmailStatus, but can also call send endpoint)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendImmediately&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EmailStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 7. Transform response back to your format&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformFromQuickBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;calculateTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;transformFromQuickBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TotalAmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qbInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MetaData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// ... more field mapping&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pain points:&lt;/strong&gt;&lt;/p&gt;

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

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



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

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

&lt;span class="c1"&gt;// Create invoice - clean, intuitive API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Switch to 'xero' or 'netsuite' anytime&lt;/span&gt;
    &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invoice_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invoice_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tax_rate_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRateId&lt;/span&gt;
      &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// List invoices with filters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUnpaidInvoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Get single invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Update invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Delete invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteInvoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code Comparison
&lt;/h3&gt;

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

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

&lt;p&gt;&lt;strong&gt;Native approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When deal closes, create invoice - native QuickBooks&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onDealWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qbAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QuickBooksAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;qbAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;qbAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRealmId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// First, find or create customer in QuickBooks&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findQuickBooksCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/customer?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;DisplayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;PrimaryEmailAddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;CompanyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;customerResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Then create the invoice&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;CustomerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SalesItemLineDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ItemRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mapProductToQBItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://quickbooks.api.intuit.com/v3/company/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;realmId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoice?minorversion=75`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle errors, send email, etc...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Apideck:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When deal closes, create invoice - with Apideck&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onDealWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create customer if needed&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If customer exists, find them&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Create invoice&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
      &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;






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

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

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

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

&lt;p&gt;Errors are normalized across all platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validation error - check error.detail&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Customer or item not found&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Duplicate invoice number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

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

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

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

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

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

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

&lt;p&gt;&lt;strong&gt;Timeline: 1 day&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You're ready to go&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Apideck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apideck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cust-456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Services&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invoice created:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




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

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

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

&lt;p&gt;&lt;strong&gt;Accounting integrations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/top-15-accounting-apis-to-integrate-with" rel="noopener noreferrer"&gt;Top 15 Accounting APIs to Integrate With&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/xero-api-pricing-and-the-app-partner-program" rel="noopener noreferrer"&gt;Xero API Pricing and the App Partner Program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/netsuite-integration-guide" rel="noopener noreferrer"&gt;NetSuite Integration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apideck.com/blog/accounting-api-integrations-for-fintech" rel="noopener noreferrer"&gt;Accounting API Integration for Fintech&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/xero" rel="noopener noreferrer"&gt;Xero&lt;/a&gt; takes a minimalist approach—you get exactly &lt;strong&gt;two tracking categories&lt;/strong&gt; per organization. That's it. No departments, no locations, no subsidiaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"region-uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"North America"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dept-uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sales"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many Xero users work around this by creating one tracking category for "Department" and another for "Region." It works, but requires careful planning since you can't add a third dimension later.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/quickbooks" rel="noopener noreferrer"&gt;QuickBooks&lt;/a&gt; supports departments, locations, AND classes—but there's a catch. In QuickBooks, "Department" often functions more like what other systems call "Location" (tracking by branch or region).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"58"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5000000000000026437"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enterprise"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, these features aren't enabled by default. Your customers need to turn on "Track locations" and "Track classes" in their QuickBooks settings.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/netsuite" rel="noopener noreferrer"&gt;NetSuite&lt;/a&gt; goes all-in with full support for departments, locations, subsidiaries, and classes. The tradeoff? Complexity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subsidiary_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Class A"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For NetSuite OneWorld (multi-subsidiary) accounts, &lt;code&gt;subsidiary_id&lt;/code&gt; is often &lt;strong&gt;required&lt;/strong&gt;. Transactions without a valid subsidiary get rejected. Your integration needs to handle this gracefully.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.apideck.com/connectors/intuit-enterprise-suite" rel="noopener noreferrer"&gt;Intuit Enterprise Suite&lt;/a&gt; (IES) is Intuit's new mid-market offering, and it takes a fundamentally different approach than QuickBooks. Instead of fixed dimension types, IES supports &lt;strong&gt;up to 20 custom dimensions&lt;/strong&gt; with unlimited values and 5 levels of hierarchy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Department"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dept-001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loc-001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proj-001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CostCenter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cc-001"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IES vs QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

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

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

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

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

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

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

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

&lt;p&gt;Before writing transactions with tracking data, query what dimensions are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fetch available dimensions for the connected account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;departments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;departments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackingCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackingCategories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

&lt;p&gt;Not every connector supports every dimension. Your code should check before including fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lineItems&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Only include tracking if supported&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;connectorSupports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;departments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;departmentId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;connectorSupports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tracking_categories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracking_categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For connector-specific fields that aren't in the unified model, you can use &lt;a href="https://developers.apideck.com/guides/pass-through" rel="noopener noreferrer"&gt;pass_through&lt;/a&gt; to send them directly to the downstream API.&lt;/p&gt;

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

&lt;p&gt;Real-world invoices often span multiple departments or projects. Most platforms support tracking at the line item level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sales consulting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"department_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project-a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Technical implementation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"department_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tracking_categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project-a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line item values override header values, giving you granular control.&lt;/p&gt;

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

&lt;p&gt;For enterprise platforms like NetSuite and Sage Intacct, check if subsidiary is required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;netsuite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subsidiaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subsidiaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subsidiaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subsidiary_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subsidiary_id is required for multi-subsidiary accounts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Cache Dimension Lists
&lt;/h3&gt;

&lt;p&gt;Tracking dimensions change infrequently. Cache them to avoid unnecessary API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;departments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`departments:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;consumerId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;accounting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;departments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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




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

</description>
      <category>api</category>
      <category>accounting</category>
      <category>accountingapi</category>
      <category>unifiedapi</category>
    </item>
  </channel>
</rss>
