Sage Authentication Patterns: Intacct vs Business Cloud vs X3
How do authentication patterns differ across Sage products (Intacct, Business Cloud, X3)?
TL;DR
Bottom line: Each Sage product uses a fundamentally different authentication model: Intacct uses dual-tier XML sessions (Sender ID + Company credentials) or OAuth 2.0 for REST; Business Cloud Accounting uses standard OAuth 2.0 authorization code flow; X3 supports basic auth, certificate auth, or OAuth2 depending on deployment (cloud mandates OAuth2).
Key limit: Sage Intacct defaults to 2 concurrent API connections per tenant -- a third request waits 30 seconds then errors with GW-0010 (429).
Watch out for: Sage Intacct's XML API and REST API use completely different authentication mechanisms -- XML uses Sender ID + session-based auth, REST uses OAuth 2.0. They are NOT interchangeable.
Best for: Teams integrating with multiple Sage products who need to understand which auth pattern applies to which product and how credential management differs.
Authentication: Intacct XML: Sender ID + getAPISession (session-based). Intacct REST: OAuth 2.0 (authorization code or client credentials). Business Cloud: OAuth 2.0 (authorization code). X3: Basic/Certificate/OAuth2 (deployment-dependent).
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.
System
Role
API Surface
Auth Method
Sage Intacct
Cloud financial management (mid-market)
XML API v3.0 + REST API
XML: Sender ID + Session; REST: OAuth 2.0 [src1, src2]
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]
Flow
Use When
Credential Lifetime
Refresh?
Notes
Login Auth (Company + User credentials)
First request or simple scripts
Per-request
N/A
Not 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 timeout
Returns 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]
Flow
Use When
Token Lifetime
Refresh?
Notes
Authorization Code
User-context operations, interactive apps
Access: 1 hour; Refresh: 6 months
Yes
Standard OAuth 2.0; requires redirect URI
Authorization Code + PKCE
Mobile apps, SPAs, public clients
Access: 1 hour; Refresh: 6 months
Yes
Required when client secret cannot be stored safely
Sage Intacct XML vs REST are completely separate auth systems: A Sender ID used for XML API calls does NOT work with the REST API, and vice versa. OAuth 2.0 tokens from the REST API cannot be used with the XML gateway endpoint. Plan for two credential sets if using both APIs. [src1, src2]
Sage Intacct session IDs are endpoint-specific: When getAPISession returns a session ID, it also returns a unique endpoint URL (e.g., na12.intacct.com). You MUST use this endpoint for all subsequent calls in that session. [src1]
Sage X3 nodelocal.js supports multiple auth modes: You can enable both basic and OAuth2 simultaneously with auth: ["basic", "oauth2"], but certificate auth always takes precedence when a client certificate is presented. [src3]
Sage Business Cloud tokens require three headers: Every API request needs the Access Token (Authorization header), a developer subscription ID, and a resource owner ID. Missing any one returns 401. [src5]
Sage Intacct REST OAuth client credentials flow still requires a username parameter -- unlike standard OAuth 2.0 client credentials, you cannot just send client_id and client_secret alone. [src2]
Constraints
Sage Intacct XML API requires a paid Web Services Developer License -- without it you cannot obtain a Sender ID, and there is no free tier for development. [src1, src7]
Sage Intacct concurrent connections default to 2 -- the third concurrent request waits 30 seconds, then fails with error GW-0010 (HTTP 429). Higher concurrency requires a Performance Tier upgrade at additional cost. [src1, src6]
Sage X3 basic auth is NOT available on Sage X3 Online (cloud) -- cloud deployments mandate OAuth2. Migrating from on-premise basic auth to cloud requires implementing OAuth2 from scratch. [src3]
Sage Business Cloud Accounting has no server-to-server flow -- all integrations must use authorization code flow, which requires initial user interaction. There is no client credentials grant for unattended access. [src5]
Sage Intacct session timeout is configurable per company -- there is no fixed global timeout. Default is ~30 minutes of inactivity, but admins can change it. Do not hardcode timeout values. [src1]
Sage Intacct API transactions are metered and billed -- Tier 1 includes 100,000 transactions/month. Each create, update, delete, query, or readByQuery counts as 1 transaction. Overages cost $0.15 per 10-transaction pack. [src6]
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
Capability
Sage Intacct (XML)
Sage Intacct (REST)
Business Cloud Accounting
Sage X3 (Cloud)
Sage X3 (On-Prem)
Auth Method
Sender ID + Session
OAuth 2.0
OAuth 2.0
OAuth2 Bearer
Basic / Cert / OAuth2
Token Type
Session ID (opaque)
JWT
Opaque
Bearer token
Session / Bearer
Token Lifetime
~30 min idle (configurable)
1h access; 6mo refresh
~1h access
Session-based
Session-based
Server-to-Server
Yes
Yes (client creds + username)
No (auth code only)
Yes (OAuth2)
Yes (basic/cert/OAuth2)
MFA Support
N/A (service credentials)
Via OAuth IdP
Via OAuth IdP
Via OAuth2 IdP
N/A (basic); Via IdP (OAuth2)
Credential Rotation
Manual (Sender password)
Standard OAuth
Standard OAuth
IdP-managed
Manual
Rate Limit
2 concurrent / 100K txn/month
Same pool as XML
429 with Retry-After
Syracuse pool
Syracuse pool
Gateway URL
api.intacct.com/ia/xml/xmlgw.phtml
api.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]
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]
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
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
Configuration
Sage Intacct (XML)
Sage Intacct (REST)
Business Cloud
Sage X3 (Cloud)
Sage X3 (On-Prem)
grant_type
N/A (session-based)
client_credentials or authorization_code
authorization_code
Per OAuth2 IdP
Per IdP / N/A (basic)
Primary credential
Sender ID + Password
Client ID + Client Secret
Client ID + Client Secret
OAuth2 Bearer Token
Username:Password or Certificate CN
Secondary credential
Company ID + User ID + Password
Username (in token request)
Subscription ID + Resource Owner ID
N/A
N/A
Token endpoint
N/A
api.intacct.com/ia/api/v1/oauth2/token
oauth.accounting.sage.com/token
External OAuth2 provider
External OAuth2 provider or N/A
API base URL
api.intacct.com/ia/xml/xmlgw.phtml
api.intacct.com/ia/api/v1/
api.accounting.sage.com/v3.1/
{host}:{port}/api1/
{host}:{port}/api1/
Config file
N/A (cloud-only)
N/A (cloud-only)
N/A (cloud-only)
N/A (cloud-managed)
nodelocal.js (session.auth)
Data Type Gotchas
Sage Intacct XML API returns session-specific endpoints: The gateway URL api.intacct.com is only for initial auth. After getAPISession, you must use the returned endpoint. Using the wrong endpoint causes session validation failures. [src1]
Sage Business Cloud requires three separate header values: Access Token (Authorization), developer subscription ID, and resource owner ID must all be present. [src5]
Sage X3 nodelocal.js auth values are case-sensitive arrays: Use auth: ["basic", "oauth2"] for multiple methods. An invalid value silently disables authentication. [src3]
Error Handling & Failure Points
Common Error Codes
Code
Product
Meaning
Cause
Resolution
GW-0010 (429)
Sage Intacct
Rate limit / concurrency exceeded
More concurrent requests than tenant allows (default: 2)
Re-authenticate with getAPISession; use the returned endpoint
XL03000004
Sage Intacct XML
Invalid Sender ID or password
Wrong Sender credentials or license expired
Verify Sender ID in Company > Admin > Web Services Sender; check license status [src7]
401
All products
Unauthorized
Token expired, invalid, or missing required headers
Refresh token; re-authenticate; check all required headers
403
All products
Forbidden
User lacks permissions for the requested resource
Check user roles and entity-level permissions
OAUTH2_INVALID_GRANT
Sage Intacct REST
Invalid grant
Authorization code expired or already used; refresh token expired
Re-initiate OAuth flow; refresh tokens expire after 6 months of non-use [src2]
Failure Points in Production
Sage Intacct session timeout breaks long-running integrations: Sessions expire after the configured idle timeout (~30 minutes default). Fix: Monitor the session timeout value returned in each API response; re-authenticate proactively before expiry. [src1]
Sage Intacct Sender ID license expiration: The Web Services Developer License must be renewed annually. If it lapses, all XML API integrations fail silently with authentication errors. Fix: Set renewal reminders 60 days before expiry; monitor for XL03000004 errors. [src7]
Sage Business Cloud refresh token expiration: If no API calls are made for an extended period, the refresh token can expire, requiring manual user re-authorization. Fix: Implement a scheduled token refresh (e.g., weekly) even if no data sync is needed. [src5]
Sage X3 cloud migration breaks basic auth integrations: When migrating from on-premise to Sage X3 Online, all basic auth integrations stop working because X3 Online only supports OAuth2. Fix: Plan OAuth2 migration before cloud migration; test OAuth2 auth against on-premise first. [src3, src4]
Sage Intacct entity restrictions hide data silently: If the API user's entity access is restricted, queries return empty results (not errors) for entities they cannot access. Fix: Verify entity-level permissions for the API user; test with queries that should return known data. [src1, src7]
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
Mixing Sage Intacct XML and REST credentials: The XML API uses Sender ID + Company credentials; the REST API uses OAuth 2.0 Client ID + Client Secret. These are completely separate credential systems. Fix: Maintain separate credential stores for each API surface. [src1, src2]
Not monitoring Sage Intacct transaction counts: API transactions are metered monthly. A poorly optimized integration can exhaust Tier 1's 100K transactions mid-month. Fix: Monitor usage in Company > Admin > Usage Insights. Batch operations where possible. [src6]
Ignoring Sage Intacct entity restrictions: API users can be restricted to specific entities. Queries return empty results (not errors) for inaccessible entities. Fix: Configure the API user with Top Level entity access, or explicitly list all entities needed. [src1, src7]
Forgetting to implement token refresh for Sage Business Cloud: Access tokens expire after ~1 hour. Without automatic refresh, integrations fail. Fix: Implement a token refresh check before every API call batch. Store the refresh token securely. [src5]
Using basic auth for Sage X3 Online: Basic auth is NOT supported on X3 Online. Developers testing on-premise find their integration fails completely on cloud. Fix: Always use OAuth2 for new X3 integrations, even on-premise, to ensure cloud compatibility. [src3, src4]
Not handling Sage Intacct concurrent connection limits: The default 2 concurrent connections per tenant means parallel processes compete. Fix: Implement a connection semaphore or queue. Serialize API calls where possible. Upgrade Performance Tier for high-throughput needs. [src1, src6]
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
Change
Date
Product
Impact
Migration Notes
Sage Intacct REST API with OAuth 2.0 GA
2024
Sage Intacct
New API surface
Register in Sage App Registry; coexists with XML API [src2]
Sage X3 v12 GraphQL API GA
2021 (R2)
Sage X3
New API surface
Create Connected App for JWT auth; requires v12+ [src8]
Sage X3 Online mandates OAuth2
2023
Sage X3
Breaking for basic auth
Migrate all basic auth integrations to OAuth2 before cloud migration
Building a multi-product Sage integration platform
Need detailed rate limit optimization for a single product
Product-specific rate limits card
Cross-System Comparison
Capability
Sage Intacct
Business Cloud Accounting
Sage X3
Notes
Auth Standard
Proprietary (XML) + OAuth 2.0 (REST)
OAuth 2.0
Basic/Cert/OAuth2/JWT
Intacct is only Sage product with dual auth systems
Server-to-Server
Yes (both APIs)
No (auth code only)
Yes (OAuth2/Cert)
Business Cloud limitation forces user interaction
Token Lifetime
XML: ~30 min; REST: 1 hour
~1 hour
Session-based
Intacct REST refresh tokens last 6 months
MFA Support
Via Web Services user (service account)
Via OAuth IdP
Via OAuth2 IdP
XML API bypasses MFA via service user
Credential Complexity
High (Sender ID + Company + User)
Low (Client ID + Secret)
Medium (varies)
Intacct XML is most complex
Rate Limiting
2 concurrent; 100K txn/month (Tier 1)
429 with Retry-After
Syracuse session pool
Intacct is most restrictive and metered
On-Premise Support
No (cloud-only)
No (cloud-only)
Yes (full support)
X3 is only product with on-premise option
GraphQL
No
No
Yes (v12+)
X3 is only Sage product with GraphQL
API Maturity
High (XML since ~2005; REST GA 2024)
Medium (REST v3.1)
Medium-High (SOAP/REST/GraphQL)
Intacct has longest API track record
Developer License
Required (paid) for XML API
Free (register at developer.sage.com)
Included with X3 license
Intacct XML API cost is an adoption barrier
Important Caveats
Each Sage product was developed independently (Sage Intacct was acquired in 2017, X3 was acquired in 2005). There is NO shared authentication infrastructure, SSO, or credential portability between products.
Sage Intacct's XML API and REST API coexist but share the same underlying transaction quota and concurrency limits. Using both APIs simultaneously counts against the same pool.
Sage X3's authentication landscape is fragmented across three API generations (SOAP, REST, GraphQL), each with different auth mechanisms. New integrations should target GraphQL with JWT for v12+.
Sage Business Cloud Accounting's lack of a client credentials flow means truly unattended integrations require careful refresh token management. If the refresh token expires, a human must re-authorize.
Rate limits and API quotas differ dramatically between products. Sage Intacct is the most restrictive with hard metering and overage charges; Sage X3 and Business Cloud rely on throttling (429) without explicit transaction counts.