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
- 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] |
| Sage Business Cloud Accounting | SMB cloud accounting | REST API v3.1 | OAuth 2.0 (authorization code) [src5] |
| Sage X3 | Mid-market ERP (cloud + on-premise) | SOAP + REST + GraphQL | Basic, Certificate, or OAuth2 (deployment-dependent) [src3, src4] |
API Surfaces & Capabilities
| API Surface | Product | Protocol | Auth Method | Token Lifetime | Real-time? | Notes |
| XML API | Sage Intacct | HTTPS/XML POST | Sender ID + Session | Session: ~30 min idle (configurable) | Yes | Legacy but full-featured; 100K txn/month (Tier 1) |
| REST API | Sage Intacct | HTTPS/JSON | OAuth 2.0 | Access: 1 hour; Refresh: 6 months | Yes | Newer; uses JWT tokens; GA 2024 |
| REST API v3.1 | Business Cloud Accounting | HTTPS/JSON | OAuth 2.0 | Access: ~1 hour; Refresh: varies | Yes | Only API surface available |
| SOAP Web Services | Sage X3 | HTTPS/XML | Basic, Certificate, or OAuth2 | Session-based (Syracuse pool) | Yes | Deprecated in favor of GraphQL for v12+ |
| REST Web Services | Sage X3 | HTTPS/JSON | Basic, Certificate, or OAuth2 | Session-based (Syracuse pool) | Yes | Syracuse-based; being superseded by GraphQL |
| GraphQL API | Sage X3 | HTTPS/JSON | JWT (Connected App) | JWT: per-request assertion | Yes | Requires v12+; recommended for new integrations |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Product | Notes |
| Max records per XML query | 2,000 | Sage Intacct | Use readMore/resultId for pagination [src1] |
| Max request processing time | 15 minutes | Sage Intacct | Alert if >5 min; optimize queries to <1,000 records |
| Max records manipulated per request | ~100 recommended | Sage Intacct | No hard limit but performance degrades significantly |
| Max concurrent gateway connections | 2 per tenant (default) | Sage Intacct | Excess waits 30s then errors; upgrade via Performance Tiers [src6] |
Rolling / Daily Limits
| Limit Type | Value | Window | Product |
| API transactions (Tier 1) | 100,000 | Monthly | Sage Intacct [src6] |
| API transactions (higher tiers) | Varies (up to millions) | Monthly | Sage Intacct (additional cost) |
| Overage rate | $0.15 per 10-transaction pack | Per billing cycle | Sage Intacct |
| Concurrent offline processes (Tier 1) | 1 | Per company | Sage Intacct |
| Rate limit response | HTTP 429 with Retry-After | Per window | All 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]
| 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 |
| Client Credentials | Server-to-server, no user context | Access: 1 hour | No refresh token; request new token | Requires 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]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
| Authorization Code | All integrations (only flow available) | Access: ~1 hour | Yes (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]
| Flow | Use When | Deployment | Notes |
| Basic Auth | On-premise only, simple integrations | On-premise | Enabled via nodelocal.js auth: "basic"; HTTPS required; NOT available on X3 Online |
| Certificate Auth | On-premise, high-security integrations | On-premise | Client certificate CN maps to X3 user; takes precedence over other methods |
| OAuth2 Bearer Token | Cloud (mandatory) or on-premise with IdP | Cloud + On-premise | Token in Authorization header; enabled via nodelocal.js auth: "oauth2" |
| JWT (GraphQL) | New integrations on v12+ | Cloud + On-premise | Connected App provides Client ID, Client Secret, Issuer, Audience [src8] |
Authentication Gotchas
- 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]
<!-- 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
| 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) | Implement queue with exponential backoff; consider Performance Tier upgrade [src1, src6] |
| XL03000006 | Sage Intacct XML | Invalid session | Session expired or wrong endpoint used | 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 |
| Sage Intacct GET requests blocked | 2023-01-15 | Sage Intacct | Breaking | All XML API requests must use HTTP POST [src1] |
| Sage X3 SOAP deprecated for GraphQL | 2022+ | Sage X3 | Deprecation notice | SOAP still functional but no new features; migrate to GraphQL for v12+ |
| Sage Business Cloud API v3.1 | 2023 | Business Cloud | Current version | Previous versions deprecated [src5] |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
| Integrating with multiple Sage products and need to understand auth differences | Already know which Sage product and just need implementation details | Product-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 selection | Need Sage 200 or Sage 300 authentication | business/erp-integration/sage-200-authentication/2026 |
| 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.
Related Units