Sage Authentication Patterns: Intacct vs Business Cloud vs X3

Type: ERP Integration System: Sage Intacct, Business Cloud Accounting, X3 (v12) Confidence: 0.88 Sources: 8 Verified: 2026-03-02 Freshness: 2026-03-02

TL;DR

System Profile

This card compares authentication patterns across three major Sage ERP/accounting products. Each product evolved independently and uses fundamentally different API architectures, which means authentication patterns are not transferable between products. Sage Intacct (acquired 2017) retained its proprietary XML API while adding a newer REST API with OAuth 2.0. Sage Business Cloud Accounting (formerly Sage One) uses a modern REST-only approach with OAuth 2.0. Sage X3 (mid-market ERP) runs on the Syracuse application server and supports SOAP, REST, and GraphQL APIs with deployment-dependent auth methods.

SystemRoleAPI SurfaceAuth Method
Sage IntacctCloud financial management (mid-market)XML API v3.0 + REST APIXML: Sender ID + Session; REST: OAuth 2.0 [src1, src2]
Sage Business Cloud AccountingSMB cloud accountingREST API v3.1OAuth 2.0 (authorization code) [src5]
Sage X3Mid-market ERP (cloud + on-premise)SOAP + REST + GraphQLBasic, Certificate, or OAuth2 (deployment-dependent) [src3, src4]

API Surfaces & Capabilities

API SurfaceProductProtocolAuth MethodToken LifetimeReal-time?Notes
XML APISage IntacctHTTPS/XML POSTSender ID + SessionSession: ~30 min idle (configurable)YesLegacy but full-featured; 100K txn/month (Tier 1)
REST APISage IntacctHTTPS/JSONOAuth 2.0Access: 1 hour; Refresh: 6 monthsYesNewer; uses JWT tokens; GA 2024
REST API v3.1Business Cloud AccountingHTTPS/JSONOAuth 2.0Access: ~1 hour; Refresh: variesYesOnly API surface available
SOAP Web ServicesSage X3HTTPS/XMLBasic, Certificate, or OAuth2Session-based (Syracuse pool)YesDeprecated in favor of GraphQL for v12+
REST Web ServicesSage X3HTTPS/JSONBasic, Certificate, or OAuth2Session-based (Syracuse pool)YesSyracuse-based; being superseded by GraphQL
GraphQL APISage X3HTTPS/JSONJWT (Connected App)JWT: per-request assertionYesRequires v12+; recommended for new integrations

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueProductNotes
Max records per XML query2,000Sage IntacctUse readMore/resultId for pagination [src1]
Max request processing time15 minutesSage IntacctAlert if >5 min; optimize queries to <1,000 records
Max records manipulated per request~100 recommendedSage IntacctNo hard limit but performance degrades significantly
Max concurrent gateway connections2 per tenant (default)Sage IntacctExcess waits 30s then errors; upgrade via Performance Tiers [src6]

Rolling / Daily Limits

Limit TypeValueWindowProduct
API transactions (Tier 1)100,000MonthlySage Intacct [src6]
API transactions (higher tiers)Varies (up to millions)MonthlySage Intacct (additional cost)
Overage rate$0.15 per 10-transaction packPer billing cycleSage Intacct
Concurrent offline processes (Tier 1)1Per companySage Intacct
Rate limit responseHTTP 429 with Retry-AfterPer windowAll Sage products

Authentication

Sage Intacct XML API (Legacy)

The XML API uses a unique dual-tier authentication: a Sender ID/Password pair identifies the developer/application, while Company ID + User ID + Password (or a session ID) identifies the tenant and user. [src1, src7]

FlowUse WhenCredential LifetimeRefresh?Notes
Login Auth (Company + User credentials)First request or simple scriptsPer-requestN/ANot recommended for repeated calls; use getAPISession instead [src1]
Session Auth (getAPISession)All production integrations~30 min idle timeout (configurable per company)Extend by making requests before timeoutReturns unique endpoint (e.g., na12.intacct.com) + session ID

Sage Intacct REST API

The REST API uses standard OAuth 2.0 with tokens issued as JWTs. Applications register in the Sage App Registry to obtain Client ID and Client Secret. [src2]

FlowUse WhenToken LifetimeRefresh?Notes
Authorization CodeUser-context operations, interactive appsAccess: 1 hour; Refresh: 6 monthsYesStandard OAuth 2.0; requires redirect URI
Authorization Code + PKCEMobile apps, SPAs, public clientsAccess: 1 hour; Refresh: 6 monthsYesRequired when client secret cannot be stored safely
Client CredentialsServer-to-server, no user contextAccess: 1 hourNo refresh token; request new tokenRequires username or session_id parameter [src2]

Sage Business Cloud Accounting

Standard OAuth 2.0 authorization code flow. Register at developer.sage.com to obtain Client ID and Client Secret. [src5]

FlowUse WhenToken LifetimeRefresh?Notes
Authorization CodeAll integrations (only flow available)Access: ~1 hourYes (refresh token provided)Must supply Access Token + subscription ID + resource owner ID in headers

Sage X3 (Syracuse Server)

Authentication method depends entirely on deployment model. Cloud (Sage X3 Online) mandates OAuth2. On-premise supports basic, certificate, or OAuth2 via nodelocal.js configuration. [src3, src4]

FlowUse WhenDeploymentNotes
Basic AuthOn-premise only, simple integrationsOn-premiseEnabled via nodelocal.js auth: "basic"; HTTPS required; NOT available on X3 Online
Certificate AuthOn-premise, high-security integrationsOn-premiseClient certificate CN maps to X3 user; takes precedence over other methods
OAuth2 Bearer TokenCloud (mandatory) or on-premise with IdPCloud + On-premiseToken in Authorization header; enabled via nodelocal.js auth: "oauth2"
JWT (GraphQL)New integrations on v12+Cloud + On-premiseConnected App provides Client ID, Client Secret, Issuer, Audience [src8]

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START - Authenticate with a Sage product
|-- Which Sage product?
|   |-- Sage Intacct
|   |   |-- Which API?
|   |   |   |-- XML API (legacy, full feature set)
|   |   |   |   |-- First request or simple script?
|   |   |   |   |   --> Login Auth (Company ID + User ID + Password + Sender ID)
|   |   |   |   |-- Production integration (repeated calls)?
|   |   |   |       --> getAPISession: get session ID + unique endpoint, reuse
|   |   |   |-- REST API (modern, OAuth 2.0)
|   |   |       |-- User-context (interactive)?
|   |   |       |   --> Authorization Code flow (+ PKCE for public clients)
|   |   |       |-- Server-to-server (unattended)?
|   |   |           --> Client Credentials flow (requires username param)
|   |-- Sage Business Cloud Accounting
|   |   --> OAuth 2.0 Authorization Code flow (only option)
|   |   --> Store refresh token; auto-renew access tokens before expiry
|   |-- Sage X3
|       |-- Cloud (Sage X3 Online)?
|       |   |-- SOAP/REST? --> OAuth2 Bearer Token
|       |   |-- GraphQL? --> JWT via Connected App
|       |-- On-premise?
|           |-- Simple, trusted network? --> Basic Auth
|           |-- High-security, PKI? --> Certificate Auth
|           |-- Have OAuth2 IdP? --> OAuth2 Bearer Token
|-- Using multiple Sage products?
    --> Each product has independent auth; no SSO across products
    --> Manage separate credential sets for each product

Quick Reference

CapabilitySage Intacct (XML)Sage Intacct (REST)Business Cloud AccountingSage X3 (Cloud)Sage X3 (On-Prem)
Auth MethodSender ID + SessionOAuth 2.0OAuth 2.0OAuth2 BearerBasic / Cert / OAuth2
Token TypeSession ID (opaque)JWTOpaqueBearer tokenSession / Bearer
Token Lifetime~30 min idle (configurable)1h access; 6mo refresh~1h accessSession-basedSession-based
Server-to-ServerYesYes (client creds + username)No (auth code only)Yes (OAuth2)Yes (basic/cert/OAuth2)
MFA SupportN/A (service credentials)Via OAuth IdPVia OAuth IdPVia OAuth2 IdPN/A (basic); Via IdP (OAuth2)
Credential RotationManual (Sender password)Standard OAuthStandard OAuthIdP-managedManual
Rate Limit2 concurrent / 100K txn/monthSame pool as XML429 with Retry-AfterSyracuse poolSyracuse pool
Gateway URLapi.intacct.com/ia/xml/xmlgw.phtmlapi.intacct.com/ia/api/v1/api.accounting.sage.com{host}:{port}/api1/{host}:{port}/api1/

Step-by-Step Integration Guide

1. Authenticate with Sage Intacct XML API (Session-based)

The XML API requires two-tier auth: Sender credentials identify your application, company credentials identify the tenant. First call getAPISession, then use the returned session ID and endpoint for all subsequent requests. [src1]

<!-- getAPISession request to api.intacct.com -->
<?xml version="1.0" encoding="UTF-8"?>
<request>
  <control>
    <senderid>YOUR_SENDER_ID</senderid>
    <password>YOUR_SENDER_PASSWORD</password>
    <controlid>sessionRequest</controlid>
    <uniqueid>false</uniqueid>
    <dtdversion>3.0</dtdversion>
    <includewhitespace>false</includewhitespace>
  </control>
  <operation>
    <authentication>
      <login>
        <userid>YOUR_USER_ID</userid>
        <companyid>YOUR_COMPANY_ID</companyid>
        <password>YOUR_USER_PASSWORD</password>
      </login>
    </authentication>
    <content>
      <function controlid="getSession">
        <getAPISession/>
      </function>
    </content>
  </operation>
</request>

Verify: Response contains <sessionid> and <endpoint> elements. Use the returned endpoint for all subsequent calls.

2. Authenticate with Sage Intacct REST API (OAuth 2.0)

Register your application in the Sage App Registry. For client credentials flow, POST to the token endpoint with grant_type, client_id, client_secret, and username. [src2]

# Client Credentials flow - acquire access token
curl -X POST "https://api.intacct.com/ia/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "username=YOUR_USERNAME"

# Response includes access_token (JWT, 1h) and optionally refresh_token (6 months)

Verify: Response JSON contains access_token field. Decode the JWT to confirm the exp claim is ~1 hour from now.

3. Authenticate with Sage Business Cloud Accounting (OAuth 2.0)

Register at developer.sage.com, configure redirect URI. Redirect user to authorization endpoint, exchange code for token. [src5]

# Step 1: Redirect user to authorization URL
# https://www.sageone.com/oauth2/auth/central?filter=apiv3.1
#   &response_type=code&client_id=YOUR_ID&redirect_uri=YOUR_CALLBACK

# Step 2: Exchange authorization code for access token
curl -X POST "https://oauth.accounting.sage.com/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=YOUR_CALLBACK_URL"

Verify: Response contains access_token and refresh_token. Test with GET https://api.accounting.sage.com/v3.1/contacts.

4. Authenticate with Sage X3 (OAuth2 Bearer Token)

For X3 Online (cloud), configure your OAuth2 provider, obtain a bearer token, and pass it in the Authorization header. For on-premise, enable OAuth2 in nodelocal.js first. [src3, src4]

# X3 OAuth2 - pass bearer token to Syracuse API
curl -X GET "https://your-x3-host:port/api1/x3/erp/SEED/BPCUSTOMER" \
  -H "Authorization: Bearer YOUR_OAUTH2_ACCESS_TOKEN" \
  -H "Accept: application/json"

# For on-premise: enable in nodelocal.js
# session: { auth: "oauth2", timeout: 600 }

Verify: Response returns JSON with X3 business object data. HTTP 200 confirms authentication succeeded.

Code Examples

Python: Sage Intacct XML API Session Authentication

# Input:  sender_id, sender_password, company_id, user_id, user_password
# Output: Session ID and unique endpoint for subsequent API calls

import requests
import xml.etree.ElementTree as ET

GATEWAY_URL = "https://api.intacct.com/ia/xml/xmlgw.phtml"

def get_api_session(sender_id, sender_pwd, company_id, user_id, user_pwd):
    """Authenticate and get session ID + unique endpoint."""
    xml_request = f"""<?xml version="1.0" encoding="UTF-8"?>
    <request>
      <control>
        <senderid>{sender_id}</senderid>
        <password>{sender_pwd}</password>
        <controlid>session</controlid>
        <uniqueid>false</uniqueid>
        <dtdversion>3.0</dtdversion>
      </control>
      <operation>
        <authentication>
          <login>
            <userid>{user_id}</userid>
            <companyid>{company_id}</companyid>
            <password>{user_pwd}</password>
          </login>
        </authentication>
        <content>
          <function controlid="getSession">
            <getAPISession/>
          </function>
        </content>
      </operation>
    </request>"""

    resp = requests.post(GATEWAY_URL, data=xml_request,
                         headers={"Content-Type": "application/xml"})
    resp.raise_for_status()

    root = ET.fromstring(resp.text)
    session_id = root.find(".//sessionid").text
    endpoint = root.find(".//endpoint").text
    return session_id, endpoint

Python: Sage Intacct REST API OAuth 2.0 Client Credentials

# Input:  client_id, client_secret, username
# Output: Access token for REST API calls

import requests

TOKEN_URL = "https://api.intacct.com/ia/api/v1/oauth2/token"

def get_intacct_rest_token(client_id, client_secret, username):
    """Acquire OAuth 2.0 access token for Sage Intacct REST API."""
    resp = requests.post(TOKEN_URL, data={
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
        "username": username,
    })
    resp.raise_for_status()
    return resp.json()["access_token"]

# Use the token
token = get_intacct_rest_token("MY_CLIENT_ID", "MY_SECRET", "api_user")
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
resp = requests.get(
    "https://api.intacct.com/ia/api/v1/objects/company-config/contact",
    headers=headers
)
print(resp.json())

cURL: Quick authentication test for each Sage product

# === Sage Intacct XML API ===
curl -X POST "https://api.intacct.com/ia/xml/xmlgw.phtml" \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0"?><request>...</request>'

# === Sage Business Cloud Accounting ===
curl -X GET "https://api.accounting.sage.com/v3.1/contacts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

# === Sage X3 (REST with OAuth2) ===
curl -X GET "https://your-x3-host:port/api1/x3/erp/SEED/BPCUSTOMER" \
  -H "Authorization: Bearer YOUR_OAUTH2_TOKEN" \
  -H "Accept: application/json"

Data Mapping

Authentication Configuration Reference

ConfigurationSage Intacct (XML)Sage Intacct (REST)Business CloudSage X3 (Cloud)Sage X3 (On-Prem)
grant_typeN/A (session-based)client_credentials or authorization_codeauthorization_codePer OAuth2 IdPPer IdP / N/A (basic)
Primary credentialSender ID + PasswordClient ID + Client SecretClient ID + Client SecretOAuth2 Bearer TokenUsername:Password or Certificate CN
Secondary credentialCompany ID + User ID + PasswordUsername (in token request)Subscription ID + Resource Owner IDN/AN/A
Token endpointN/Aapi.intacct.com/ia/api/v1/oauth2/tokenoauth.accounting.sage.com/tokenExternal OAuth2 providerExternal OAuth2 provider or N/A
API base URLapi.intacct.com/ia/xml/xmlgw.phtmlapi.intacct.com/ia/api/v1/api.accounting.sage.com/v3.1/{host}:{port}/api1/{host}:{port}/api1/
Config fileN/A (cloud-only)N/A (cloud-only)N/A (cloud-only)N/A (cloud-managed)nodelocal.js (session.auth)

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeProductMeaningCauseResolution
GW-0010 (429)Sage IntacctRate limit / concurrency exceededMore concurrent requests than tenant allows (default: 2)Implement queue with exponential backoff; consider Performance Tier upgrade [src1, src6]
XL03000006Sage Intacct XMLInvalid sessionSession expired or wrong endpoint usedRe-authenticate with getAPISession; use the returned endpoint
XL03000004Sage Intacct XMLInvalid Sender ID or passwordWrong Sender credentials or license expiredVerify Sender ID in Company > Admin > Web Services Sender; check license status [src7]
401All productsUnauthorizedToken expired, invalid, or missing required headersRefresh token; re-authenticate; check all required headers
403All productsForbiddenUser lacks permissions for the requested resourceCheck user roles and entity-level permissions
OAUTH2_INVALID_GRANTSage Intacct RESTInvalid grantAuthorization code expired or already used; refresh token expiredRe-initiate OAuth flow; refresh tokens expire after 6 months of non-use [src2]

Failure Points in Production

Anti-Patterns

Wrong: Using login authentication for every XML API request

# BAD - authenticates with company credentials on every call
# Wastes a transaction, slower, and risks hitting concurrency limits
for invoice in invoices:
    response = send_xml_request(
        sender_id, sender_pwd,
        company_id, user_id, user_pwd,
        create_invoice_xml(invoice)
    )

Correct: Use getAPISession once, reuse session ID

# GOOD - authenticate once, reuse session for all subsequent calls
session_id, endpoint = get_api_session(
    sender_id, sender_pwd, company_id, user_id, user_pwd
)

for invoice in invoices:
    response = send_xml_request_with_session(
        sender_id, sender_pwd, session_id,
        endpoint,  # Use the session-specific endpoint!
        create_invoice_xml(invoice)
    )

Wrong: Hardcoding the generic gateway URL for session requests

# BAD - using api.intacct.com for ALL requests after getAPISession
ENDPOINT = "https://api.intacct.com/ia/xml/xmlgw.phtml"
session_id, unique_endpoint = get_api_session(...)
# Ignoring unique_endpoint! All subsequent calls use generic URL
response = requests.post(ENDPOINT, data=session_request_xml)  # FAILS

Correct: Use the unique endpoint returned by getAPISession

# GOOD - use the endpoint returned by getAPISession
session_id, unique_endpoint = get_api_session(...)
# unique_endpoint is e.g. "https://na12.intacct.com/ia/xml/xmlgw.phtml"
response = requests.post(unique_endpoint, data=session_request_xml)  # WORKS

Wrong: Assuming Sage Business Cloud supports server-to-server auth

# BAD - trying client credentials flow with Business Cloud Accounting
# This flow does NOT exist for this product
response = requests.post("https://oauth.accounting.sage.com/token", data={
    "grant_type": "client_credentials",
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET
})
# Returns error - client_credentials not supported

Correct: Use authorization code flow with stored refresh token

# GOOD - use authorization code flow, then persist refresh token
# Initial auth requires user interaction (one-time)
# Subsequently, use refresh token for unattended access
response = requests.post("https://oauth.accounting.sage.com/token", data={
    "grant_type": "refresh_token",
    "refresh_token": stored_refresh_token,
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET
})
new_tokens = response.json()
# Store new refresh_token for next time

Common Pitfalls

Diagnostic Commands

# === Sage Intacct XML API: Test authentication ===
curl -s -X POST "https://api.intacct.com/ia/xml/xmlgw.phtml" \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
  <request>
    <control>
      <senderid>YOUR_SENDER_ID</senderid>
      <password>YOUR_SENDER_PWD</password>
      <controlid>authTest</controlid>
      <uniqueid>false</uniqueid>
      <dtdversion>3.0</dtdversion>
    </control>
    <operation>
      <authentication>
        <login>
          <userid>YOUR_USER</userid>
          <companyid>YOUR_COMPANY</companyid>
          <password>YOUR_PWD</password>
        </login>
      </authentication>
      <content>
        <function controlid="testSession">
          <getAPISession/>
        </function>
      </content>
    </operation>
  </request>'
# Expected: <sessionid> and <endpoint> in response XML

# === Sage Intacct REST API: Test OAuth token acquisition ===
curl -s -X POST "https://api.intacct.com/ia/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=YOUR_ID&client_secret=YOUR_SECRET&username=YOUR_USER"
# Expected: JSON with access_token field

# === Sage Business Cloud Accounting: Test API access ===
curl -s -X GET "https://api.accounting.sage.com/v3.1/contacts?items_per_page=1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
# Expected: JSON array of contacts; HTTP 200

# === Sage X3: Test REST API with OAuth2 bearer token ===
curl -s -X GET "https://YOUR_X3_HOST:PORT/api1/x3/erp/SEED/BPCUSTOMER?\$top=1" \
  -H "Authorization: Bearer YOUR_OAUTH2_TOKEN" \
  -H "Accept: application/json"
# Expected: JSON with customer data; HTTP 200

Version History & Compatibility

ChangeDateProductImpactMigration Notes
Sage Intacct REST API with OAuth 2.0 GA2024Sage IntacctNew API surfaceRegister in Sage App Registry; coexists with XML API [src2]
Sage X3 v12 GraphQL API GA2021 (R2)Sage X3New API surfaceCreate Connected App for JWT auth; requires v12+ [src8]
Sage X3 Online mandates OAuth22023Sage X3Breaking for basic authMigrate all basic auth integrations to OAuth2 before cloud migration
Sage Intacct GET requests blocked2023-01-15Sage IntacctBreakingAll XML API requests must use HTTP POST [src1]
Sage X3 SOAP deprecated for GraphQL2022+Sage X3Deprecation noticeSOAP still functional but no new features; migrate to GraphQL for v12+
Sage Business Cloud API v3.12023Business CloudCurrent versionPrevious versions deprecated [src5]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Integrating with multiple Sage products and need to understand auth differencesAlready know which Sage product and just need implementation detailsProduct-specific API card
Planning migration between Sage products (e.g., Sage 50 to Intacct)Need non-Sage ERP authentication (SAP, Oracle, Dynamics)Product-specific auth card (e.g., dynamics-365-authentication)
Evaluating Sage product API maturity for vendor selectionNeed Sage 200 or Sage 300 authenticationbusiness/erp-integration/sage-200-authentication/2026
Building a multi-product Sage integration platformNeed detailed rate limit optimization for a single productProduct-specific rate limits card

Cross-System Comparison

CapabilitySage IntacctBusiness Cloud AccountingSage X3Notes
Auth StandardProprietary (XML) + OAuth 2.0 (REST)OAuth 2.0Basic/Cert/OAuth2/JWTIntacct is only Sage product with dual auth systems
Server-to-ServerYes (both APIs)No (auth code only)Yes (OAuth2/Cert)Business Cloud limitation forces user interaction
Token LifetimeXML: ~30 min; REST: 1 hour~1 hourSession-basedIntacct REST refresh tokens last 6 months
MFA SupportVia Web Services user (service account)Via OAuth IdPVia OAuth2 IdPXML API bypasses MFA via service user
Credential ComplexityHigh (Sender ID + Company + User)Low (Client ID + Secret)Medium (varies)Intacct XML is most complex
Rate Limiting2 concurrent; 100K txn/month (Tier 1)429 with Retry-AfterSyracuse session poolIntacct is most restrictive and metered
On-Premise SupportNo (cloud-only)No (cloud-only)Yes (full support)X3 is only product with on-premise option
GraphQLNoNoYes (v12+)X3 is only Sage product with GraphQL
API MaturityHigh (XML since ~2005; REST GA 2024)Medium (REST v3.1)Medium-High (SOAP/REST/GraphQL)Intacct has longest API track record
Developer LicenseRequired (paid) for XML APIFree (register at developer.sage.com)Included with X3 licenseIntacct XML API cost is an adoption barrier

Important Caveats

Related Units