---
# === IDENTITY ===
id: business/erp-integration/sage-authentication-patterns/2026
canonical_question: "How do authentication patterns differ across Sage products (Intacct, Business Cloud, X3)?"
aliases:
  - "Sage Intacct API authentication vs Sage X3 vs Business Cloud Accounting"
  - "How to authenticate with Sage Intacct Web Services XML API and REST OAuth 2.0"
  - "Sage X3 Syracuse authentication methods basic certificate OAuth2 GraphQL"
  - "Sage Business Cloud Accounting API OAuth 2.0 setup and token management"
entity_type: erp_integration
domain: business > erp-integration > sage-authentication-patterns
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Sage Intacct"
    vendor: "Sage"
    version: "REST API + XML API v3.0"
    edition: "All editions (Essentials, Advanced, Premium)"
    deployment: "cloud"
    api_surface: "XML (SOAP-like) + REST (OAuth 2.0)"
  - name: "Sage Business Cloud Accounting"
    vendor: "Sage"
    version: "REST API v3.1"
    edition: "All editions"
    deployment: "cloud"
    api_surface: "REST (OAuth 2.0)"
  - name: "Sage X3"
    vendor: "Sage"
    version: "v12 (Syracuse)"
    edition: "All editions"
    deployment: "cloud + on-premise"
    api_surface: "SOAP + REST + GraphQL"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.88
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024 - Sage Intacct REST API OAuth 2.0 GA; Sage X3 v12 GraphQL GA"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Sage Intacct XML API requires a Web Services Developer License (additional cost) to obtain Sender ID credentials"
  - "Sage Intacct REST API OAuth 2.0 tokens expire after 1 hour; refresh tokens last 6 months"
  - "Sage Intacct concurrent connections default to 2 per tenant; excess requests wait 30 seconds then error"
  - "Sage X3 basic authentication is NOT available on Sage X3 Online (cloud) - OAuth2 only"
  - "Sage X3 certificate authentication requires HTTPS and is on-premise only"
  - "Sage Business Cloud Accounting OAuth 2.0 is the only authentication method - no API keys or basic auth"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs only Sage Intacct REST API authentication (not comparison)"
    use_instead: "business/erp-integration/sage-intacct-rest-api/2026"
  - condition: "User needs Sage 200 or Sage 300 authentication"
    use_instead: "business/erp-integration/sage-200-authentication/2026"
  - condition: "User needs a cross-vendor ERP authentication comparison (Sage vs SAP vs Oracle)"
    use_instead: "business/erp-integration/erp-authentication-comparison/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: sage_product
    question: "Which Sage product are you integrating with?"
    type: choice
    options:
      - "Sage Intacct (financial management)"
      - "Sage Business Cloud Accounting (SMB accounting)"
      - "Sage X3 (mid-market ERP)"
      - "Multiple Sage products (cross-product integration)"
  - key: auth_flow
    question: "What authentication flow do you need?"
    type: choice
    options:
      - "server-to-server (unattended, no user context)"
      - "user-context (delegated, authorization code flow)"
      - "session-based (XML API with Sender ID)"
  - key: deployment
    question: "What is the deployment model?"
    type: choice
    options:
      - "Cloud (SaaS)"
      - "On-premise"
      - "Hybrid"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/sage-authentication-patterns/2026"
suggested_citation: "Source: knowledgelib.io - AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/sage-intacct-rest-api/2026"
      label: "Sage Intacct REST API capabilities and rate limits"
    - id: "business/erp-integration/sage-x3-rest-api/2026"
      label: "Sage X3 REST and GraphQL API capabilities"
  solves:
    - id: "business/erp-integration/sage-intacct-netsuite-migration/2026"
      label: "Sage Intacct to NetSuite migration playbook"
  alternative_to:
    - id: "business/erp-integration/dynamics-365-authentication/2026"
      label: "Dynamics 365 authentication via Azure AD/Entra ID (different vendor)"
  often_confused_with:
    - id: "business/erp-integration/sage-200-authentication/2026"
      label: "Sage 200 API authentication (different product line, different auth flow)"

# === SOURCES ===
sources:
  - id: src1
    title: "Sage Intacct Web Services Overview"
    author: Sage
    url: https://developer.intacct.com/web-services/
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src2
    title: "OAuth 2.0 authorization - Sage Intacct REST API"
    author: Sage
    url: https://developer.sage.com/intacct/docs/1/sage-intacct-rest-api/authorization-and-security/oauth2
    type: official_docs
    published: 2025-10-15
    reliability: authoritative
  - id: src3
    title: "Sage X3 Web Services Integration Overview (v12)"
    author: Sage
    url: https://online-help.sagex3.com/erp/12/en-us/Content/V7DEV/integration-guide_ws-overview.html
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src4
    title: "Sage X3 OAuth2 Authentication for Web Services"
    author: Sage
    url: https://online-help.sagex3.com/erp/12/en-us/Content/V7DEV/integration-guide_ws-oauth2-authentication.html
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src5
    title: "Sage Business Cloud Accounting API Authentication Guide"
    author: Sage
    url: https://developer.sage.com/accounting/guides/authenticating/authentication
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src6
    title: "Sage Intacct API Limits: What Are They? Why Should You Care?"
    author: RKL eSolutions
    url: https://www.rklesolutions.com/blog/sage-intacct-api-limits-what-are-they-why-should-you-care
    type: technical_blog
    published: 2025-03-15
    reliability: moderate_high
  - id: src7
    title: "How to Get Your Sage Intacct API Keys"
    author: Apideck
    url: https://www.apideck.com/blog/how-to-get-your-sage-intacct-api-key
    type: technical_blog
    published: 2025-01-20
    reliability: moderate_high
  - id: src8
    title: "Sage X3 GraphQL API Authentication Reference"
    author: Sage
    url: https://developer.sage.com/x3/graphql/reference/authentication
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
---

# Sage Authentication Patterns: Intacct vs Business Cloud vs 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). [src1, src2, src5, src3]
- **Key limit**: Sage Intacct defaults to 2 concurrent API connections per tenant -- a third request waits 30 seconds then errors with GW-0010 (429). [src1, src6]
- **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. [src1, src2]
- **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). [src1, src2, src5, src3]

## 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. [src1, src2, src3, src5]

| 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 timeout (configurable) | Yes | Legacy but full-featured; 100K txn/month (Tier 1) [src1, src6] |
| REST API | Sage Intacct | HTTPS/JSON | OAuth 2.0 | Access: 1 hour; Refresh: 6 months | Yes | Newer; uses JWT tokens; GA 2024 [src2] |
| REST API v3.1 | Business Cloud Accounting | HTTPS/JSON | OAuth 2.0 | Access: ~1 hour; Refresh: varies | Yes | Only API surface available [src5] |
| SOAP Web Services | Sage X3 | HTTPS/XML | Basic, Certificate, or OAuth2 | Session-based (Syracuse pool) | Yes | Deprecated in favor of GraphQL for v12+ [src3] |
| REST Web Services | Sage X3 | HTTPS/JSON | Basic, Certificate, or OAuth2 | Session-based (Syracuse pool) | Yes | Syracuse-based; being superseded by GraphQL [src3] |
| GraphQL API | Sage X3 | HTTPS/JSON | JWT (Connected App) | JWT: per-request assertion | Yes | Requires v12+; recommended for new integrations [src8] |

## 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 [src1] |
| Max records manipulated per request | ~100 recommended | Sage Intacct | No hard limit but performance degrades significantly [src1] |
| Max concurrent gateway connections | 2 per tenant (default) | Sage Intacct | Excess waits 30s then errors; upgrade via Performance Tiers [src1, src6] |

[src1, 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) [src6] |
| Overage rate | $0.15 per 10-transaction pack | Per billing cycle | Sage Intacct [src6] |
| Concurrent offline processes (Tier 1) | 1 | Per company | Sage Intacct [src6] |
| Rate limit response | HTTP 429 with Retry-After | Per window | All Sage products [src1, src5] |

[src1, src6]

## 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. This two-level approach is unlike any other major ERP vendor. [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 [src1] |

### 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 [src2] |
| Authorization Code + PKCE | Mobile apps, SPAs, public clients | Access: 1 hour; Refresh: 6 months | Yes | Required when client secret cannot be stored safely [src2] |
| 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 in Authorization header + subscription ID + resource owner ID [src5] |

### 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 [src3] |
| Certificate Auth | On-premise, high-security integrations | On-premise | Client certificate CN maps to X3 user; takes precedence over other methods [src3] |
| OAuth2 Bearer Token | Cloud (mandatory) or on-premise with IdP | Cloud + On-premise | Enabled via nodelocal.js `auth: "oauth2"`; token in Authorization header [src3, src4] |
| JWT (GraphQL) | New integrations on v12+ | Cloud + On-premise | Connected App provides Client ID, Client Secret, Issuer, Audience [src8] |

[src3, src4, 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 -- the generic `api.intacct.com` endpoint will not accept the session ID. [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 or session_id 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) [src1]
|   |   |   |   |-- Production integration (repeated calls)?
|   |   |   |   |   --> getAPISession: get session ID + unique endpoint, reuse [src1]
|   |   |   |   |-- Need to manage session lifecycle?
|   |   |   |       --> Monitor session timeout; re-authenticate before expiry [src1]
|   |   |   |-- REST API (modern, OAuth 2.0)
|   |   |       |-- User-context (interactive)?
|   |   |       |   --> Authorization Code flow (+ PKCE for public clients) [src2]
|   |   |       |-- Server-to-server (unattended)?
|   |   |           --> Client Credentials flow (requires username param) [src2]
|   |-- Sage Business Cloud Accounting
|   |   |-- All integrations
|   |       --> OAuth 2.0 Authorization Code flow (only option) [src5]
|   |       --> Store refresh token; auto-renew access tokens before expiry
|   |-- Sage X3
|       |-- Cloud (Sage X3 Online)?
|       |   |-- SOAP/REST Web Services?
|       |   |   --> OAuth2 Bearer Token [src4]
|       |   |-- GraphQL API (v12+)?
|       |       --> JWT via Connected App (Client ID + Secret + Issuer + Audience) [src8]
|       |-- On-premise?
|           |-- Simple integration, trusted network?
|           |   --> Basic Auth (nodelocal.js auth: "basic") [src3]
|           |-- High-security, PKI infrastructure available?
|           |   --> Certificate Auth (CN maps to X3 user) [src3]
|           |-- Have an OAuth2 IdP (Azure AD, Okta)?
|               --> OAuth2 Bearer Token (nodelocal.js auth: "oauth2") [src4]
|-- 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-Premise) |
|---|---|---|---|---|---|
| **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) | 1 hour access; 6 month refresh | ~1 hour access | Session-based | Session-based |
| **Server-to-Server** | Yes (Sender ID + getAPISession) | Yes (client credentials + 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 (rotate client secret) | 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/ |

[src1, src2, src5, src3, src4, src6]

## 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]

```xml
<!-- Step 1: 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 (e.g., `https://na12.intacct.com/ia/xml/xmlgw.phtml`) for all subsequent calls.

### 2. Authenticate with Sage Intacct REST API (OAuth 2.0)

Register your application in the Sage App Registry to get Client ID and Client Secret. For client credentials flow, POST to the token endpoint with grant_type, client_id, client_secret, and username. [src2]

```bash
# 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]

```bash
# Step 1: Redirect user to authorization URL
# https://www.sageone.com/oauth2/auth/central?filter=apiv3.1
#   &response_type=code
#   &client_id=YOUR_CLIENT_ID
#   &redirect_uri=YOUR_CALLBACK_URL
#   &scope=full_access

# 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` with Authorization header.

### 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]

```bash
# 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

```python
# 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>
        <includewhitespace>false</includewhitespace>
      </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

# Usage
session_id, endpoint = get_api_session(
    "MY_SENDER", "sender_pass", "COMP_ID", "api_user", "user_pass"
)
print(f"Session: {session_id[:20]}... Endpoint: {endpoint}")
```

### Python: Sage Intacct REST API OAuth 2.0 Client Credentials

```python
# 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()
    token_data = resp.json()
    return token_data["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

```bash
# === Sage Intacct XML API ===
# Input:  Sender ID, password, company credentials
# Output: Session ID in XML response

curl -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>test</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>'

# === Sage Business Cloud Accounting ===
# Input:  Valid access token
# Output: JSON response from contacts endpoint

curl -X GET "https://api.accounting.sage.com/v3.1/contacts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

# === Sage X3 (REST/SOAP with OAuth2) ===
# Input:  Valid OAuth2 bearer token, X3 host
# Output: JSON response from customer endpoint

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` (then session-specific) | `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) |

[src1, src2, src5, src3, src4]

### 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 (e.g., `na12.intacct.com`). 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. The subscription ID is obtained during app registration, not during OAuth flow. [src5]
- **Sage X3 nodelocal.js auth values are case-sensitive arrays**: Use `auth: ["basic", "oauth2"]` for multiple methods. A single string `auth: "basic"` also works. 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 [src1] |
| 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 [src1, src7] |
| 401 | All products | Unauthorized | Token expired, invalid, or missing required headers | Refresh token (REST/OAuth); re-authenticate (XML); check all required headers [src2, src5] |
| 403 | All products | Forbidden | User lacks permissions for the requested resource | Check user roles and entity-level permissions; verify subscription scope [src1, src5] |
| 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] |

[src1, src2, src5, src6]

### Failure Points in Production

- **Sage Intacct session timeout breaks long-running integrations**: Sessions expire after the configured idle timeout (~30 minutes default). A batch job pausing between API calls can lose its session. Fix: `Monitor the session timeout value returned in each API response; re-authenticate proactively before expiry. Alternatively, use the REST API with token refresh.` [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 in logs.` [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, to keep the refresh token alive.` [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 (nodelocal.js supports both simultaneously).` [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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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. Using XML Sender credentials with the REST token endpoint (or vice versa) results in authentication failure. Fix: `Maintain separate credential stores for each API surface. Document which credentials map to which API in your integration config.` [src1, src2]
- **Not monitoring Sage Intacct transaction counts**: API transactions are metered monthly. Each query/create/update/delete counts as one transaction. A poorly optimized integration can exhaust Tier 1's 100K transactions mid-month, causing overages. Fix: `Monitor usage in Company > Admin > Usage Insights. Batch operations where possible to reduce transaction counts. Set alerts at 75% utilization.` [src6]
- **Ignoring Sage Intacct entity restrictions**: API users can be restricted to specific entities. Queries return empty results (not errors) for inaccessible entities, leading to silent data loss. Fix: `Configure the API user with Top Level entity access, or explicitly list all entities needed. Test with known data across all entities.` [src1, src7]
- **Forgetting to implement token refresh for Sage Business Cloud**: Access tokens expire after ~1 hour. Without automatic refresh token rotation, integrations fail after an hour. Fix: `Implement a token refresh check before every API call batch. Store the refresh token securely and update it when refreshed.` [src5]
- **Using basic auth for Sage X3 Online**: Basic auth is NOT supported on X3 Online. Developers testing on-premise with basic auth find their integration fails completely when deployed to cloud. Fix: `Always use OAuth2 for new X3 integrations, even on-premise, to ensure cloud compatibility. Configure nodelocal.js with auth: "oauth2" from the start.` [src3, src4]
- **Not handling Sage Intacct concurrent connection limits**: The default 2 concurrent connections per tenant means parallel processes compete. A third connection waits 30 seconds then errors. Fix: `Implement a connection semaphore or queue. Serialize API calls where possible. Upgrade Performance Tier for high-throughput needs.` [src1, src6]

## Diagnostic Commands

```bash
# === 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 token and 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

# === Check Sage Intacct API usage (in-product only) ===
# Navigate to: Company > Admin > Usage Insights > API Transaction Usage
# Shows: Monthly transactions by sender ID, application type, and trend
```

## 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 [src3, src4] |
| Sage Intacct GET requests blocked | 2023-01-15 | Sage Intacct | Breaking | All XML API requests must use HTTP POST [src1] |
| Sage X3 SOAP deprecated in favor of GraphQL | 2022+ | Sage X3 | Deprecation notice | SOAP still functional but no new features; migrate to GraphQL for v12+ [src3] |
| Sage Business Cloud Accounting API v3.1 | 2023 | Business Cloud | Current version | Previous versions (v1, v2) deprecated [src5] |

[src1, src2, src3, src8]

### Deprecation Policy

Sage does not publish a uniform deprecation policy across products. Sage Intacct's XML API remains fully supported alongside the REST API with no announced sunset. Sage X3's SOAP web services are in maintenance mode for v12+ (GraphQL is the strategic direction). Sage Business Cloud Accounting API versions are deprecated with migration windows of 12-24 months. Always check the Sage Developer Portal for each product's specific deprecation notices. [src1, src3, 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 (e.g., business/erp-integration/sage-intacct-rest-api/2026) |
| 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., business/erp-integration/dynamics-365-authentication/2026) |
| 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 [src1, src2] |
| **Server-to-Server** | Yes (both APIs) | No (auth code only) | Yes (OAuth2/Cert) | Business Cloud limitation forces user interaction [src5] |
| **Token Lifetime** | XML: ~30 min; REST: 1 hour | ~1 hour | Session-based | Intacct REST refresh tokens last 6 months [src2] |
| **MFA Support** | Via Web Services user (service account) | Via OAuth IdP | Via OAuth2 IdP | XML API bypasses MFA via service user [src1] |
| **Credential Complexity** | High (Sender ID + Company + User) | Low (Client ID + Secret) | Medium (varies by method) | Intacct XML is most complex [src1, src7] |
| **Rate Limiting** | 2 concurrent; 100K txn/month (Tier 1) | 429 with Retry-After | Syracuse session pool | Intacct is most restrictive and metered [src6] |
| **On-Premise Support** | No (cloud-only) | No (cloud-only) | Yes (full support) | X3 is only product with on-premise option [src3] |
| **GraphQL** | No | No | Yes (v12+) | X3 is only Sage product with GraphQL [src8] |
| **API Maturity** | High (XML since ~2005; REST GA 2024) | Medium (REST v3.1) | Medium-High (SOAP/REST/GraphQL) | Intacct has longest API track record [src1, src3] |
| **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 [src7] |

## 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. [src1, src3, src5]
- 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. [src1, src6]
- 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+. [src3, src8]
- 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. [src5]
- 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. [src1, src6]

## Related Units

- [Sage Intacct REST API capabilities and rate limits](/business/erp-integration/sage-intacct-rest-api/2026)
- [Sage X3 REST and GraphQL API capabilities](/business/erp-integration/sage-x3-rest-api/2026)
- [Dynamics 365 authentication via Azure AD/Entra ID](/business/erp-integration/dynamics-365-authentication/2026)
- [Sage Intacct to NetSuite migration playbook](/business/erp-integration/sage-intacct-netsuite-migration/2026)
