This card covers authentication for all Infor CloudSuite products that use Infor OS and the ION API Gateway as their integration platform. This includes CloudSuite Industrial (SyteLine), CloudSuite Distribution, CloudSuite Fashion, M3, and LN, among others. The ION API Gateway acts as a centralized OAuth 2.0 authorization server, while Infor Federated Services (IFS) handles identity federation and claims management. This card does NOT cover on-premises Infor OS deployments (which use IFS with ADFS/claims-based auth) or IFS Cloud from IFS AB (a completely different vendor and product).
| Property | Value |
|---|---|
| Vendor | Infor |
| System | Infor OS / ION API Gateway (2025.x / 2026.x) |
| API Surface | REST (ION API Gateway) |
| Current API Version | ION API 2025.x / 2026.x |
| Editions Covered | All CloudSuite editions (Industrial, Distribution, Fashion, M3, LN) |
| Deployment | Cloud (Infor multi-tenant) |
| API Docs | Infor ION API Gateway Administration Guide |
| Status | GA |
The ION API Gateway is the single entry point for all API traffic to Infor CloudSuite applications. All external API calls must be authenticated through the gateway using OAuth 2.0 Bearer tokens.
| API Surface | Protocol | Best For | Auth Flows Supported | Real-time? | Notes |
|---|---|---|---|---|---|
| ION API Gateway (REST) | HTTPS/JSON | All external API integration | All four OAuth 2.0 grants | Yes | Central gateway for all Infor CloudSuite APIs |
| ION Connect (Messaging) | ION messaging | Event-driven, async document exchange | SAML Bearer (via ION) | Async | BODs (Business Object Documents) for ERP events |
| Infor Data Lake | HTTPS/JSON | Analytics, reporting, bulk data export | Backend Service (Resource Owner) | No (batch) | Read-only access to replicated ERP data |
| Infor Ming.le APIs | HTTPS/JSON | Portal widgets, embedded apps | SAML Bearer (SSO token reuse) | Yes | Apps embedded in Ming.le portal reuse SSO token |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Default request timeout | 1 minute | All ION API Gateway calls | Extensible to max 5 minutes via policy [src7] |
| Max timeout (buffered policies) | 5 minutes | Policies with buffering enabled | Hard ceiling, cannot be extended further [src7] |
| Payload limit (buffered policies) | 10 MB | Endpoints with buffered policies | Unbuffered endpoints have no payload limit [src7] |
| Payload limit (unbuffered) | No limit | Standard API Gateway calls | Subject to backend service limits [src7] |
| Limit Type | Default Value | Window | Configuration |
|---|---|---|---|
| Quota (requests) | 1,000 (configurable) | 60 seconds (configurable) | Per-user or global, set via endpoint policy [src4] |
| Spike arrest | 1,000 requests/period (configurable) | 60,000 ms (configurable) | Rate smoothing delays after threshold [src4] |
| Rate smoothing delay | Configurable (ms) | Per-request after threshold | Adds delay to requests exceeding delayAfterCount [src4] |
| Cache response TTL | 3,600 seconds (configurable) | Per-endpoint | Reduces backend load for repeated queries [src4] |
All ION API Gateway authentication flows are mediated by Infor Federated Services (IFS), which acts as the claims provider and identity federation hub. The IFS authorization server issues OAuth 2.0 tokens after authenticating the user or service account.
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Resource Owner Grant (Backend Service) | Server-to-server, no user interaction, daemons, ETL | Access: ~2h | Only if enabled; rotates every ~8h | Requires 4 credentials: ClientID, ClientSecret, saak, sask. Recommended for integrations. [src2, src3] |
| Authorization Code Grant | Web apps, mobile/desktop apps requiring user login | Access: ~2h; Refresh: configurable | Yes (if enabled) | Requires redirect URI registration. User authenticates via IFS. [src1, src2] |
| SAML Bearer Grant | Apps embedded in Infor Ming.le portal (already SSO'd) | Access: ~2h | Depends on SAML assertion | Reuses the SSO token from Ming.le login. Scopes derived from SAML assertion. [src2] |
| Implicit Grant | Single-page applications (SPAs) | Access: ~2h | No | Less secure; being deprecated industry-wide. Use Authorization Code + PKCE where possible. [src3] |
START -- Authenticate with Infor CloudSuite via ION API Gateway
|-- What type of application?
| |-- Backend service / daemon / ETL (no user present)
| | |-- Have .ionapi credentials file?
| | | |-- YES --> Resource Owner Grant using saak + sask + ci + cs
| | | |-- NO --> Register Backend Service app in ION API Gateway,
| | | enable "Create Service Account", download .ionapi
| | |-- Need long-running session (>2h)?
| | |-- YES --> Enable "Issue Refresh Token" on the authorized app
| | |-- NO --> Request new access token per session (simple)
| |-- Web application (user login required)
| | |-- Server-side web app
| | | --> Authorization Code Grant (register as Web Application type)
| | |-- SPA / client-side only
| | --> Implicit Grant (consider Auth Code + PKCE if supported)
| |-- App embedded in Infor Ming.le portal
| | --> SAML Bearer Grant (reuse SSO token from Ming.le)
| |-- Infor Data Lake / ETL extraction
| --> Backend Service (Resource Owner) with .ionapi file
|-- Which credential file format?
| |-- .ionapi file (JSON with ti, cn, ci, cs, iu, pu, oa, ot, saak, sask)
| | --> Parse file, extract endpoints and credentials
| |-- Manual ClientID/Secret (registered but no .ionapi)
| --> Obtain token endpoint from Infor OS admin portal
|-- Token refresh strategy?
|-- Short jobs (<2h) --> No refresh needed, single token per job
|-- Long jobs (>2h) --> Enable refresh tokens, handle rotation
|-- Idle >30 days --> Must re-authenticate; refresh token auto-invalidated
| Operation | Endpoint / Config | Value | Notes |
|---|---|---|---|
| Authorization endpoint | {pu}/{oa} | From .ionapi file | Tenant-specific URL |
| Token endpoint | {pu}/{ot} | From .ionapi file | POST with form-encoded params |
| Token revocation | {pu}/revoke_token.oauth2 | POST | Send refresh_token + token_type_hint=refresh_token |
| IFS Service API test | GET /ifsservice/usermgt/v2/users/me | Returns current user | Best endpoint to verify auth works |
| .ionapi portal URL | pu | Base URL for auth endpoints | Tenant-specific, do not hardcode |
| .ionapi client ID | ci | OAuth 2.0 client identifier | From authorized app registration |
| .ionapi client secret | cs | OAuth 2.0 client secret | Sensitive, never commit to VCS |
| .ionapi service account key | saak | Service account access key | Backend Service grant only |
| .ionapi service account secret | sask | Service account secret key | Backend Service grant only |
| .ionapi tenant ID | ti | Infor tenant identifier | Multi-tenant environment ID |
| .ionapi ION API URL | iu | Base URL for API calls | Target for all API requests after auth |
| Default access token TTL | ~2 hours | Configurable by admin | Re-request before expiry |
| Default refresh token rotation | ~8 hours | Configurable | Store latest rotated token |
Navigate to Infor OS > API Gateway > Authorized Apps. Create a new authorized application. Select "Backend Service" for server-to-server integrations. Enable "Create Service Account" and associate it with an IFS user. Download the .ionapi credentials file. [src2, src6]
Infor OS steps:
1. Log in to Infor OS (your CloudSuite portal)
2. Navigate to Burger menu > API Gateway > Authorized Apps
3. Click "+" to create new authorized app
4. For Backend Service:
a. Select type: "Backend Service"
b. Enable "Create Service Account"
c. Select user to associate with service account
d. Optionally add scopes
5. Save and download the .ionapi credentials file
6. Store the .ionapi file securely
Verify: Open the .ionapi file and confirm it contains ci, cs, pu, oa, ot fields. For backend services, also confirm saak and sask are present.
The .ionapi file is a JSON file containing all endpoints and credentials needed for authentication. [src3]
import json
def parse_ionapi(filepath):
with open(filepath, 'r') as f:
creds = json.load(f)
return {
'client_id': creds['ci'],
'client_secret': creds['cs'],
'token_endpoint': f"{creds['pu']}/{creds['ot']}",
'ionapi_url': creds.get('iu', ''),
'saak': creds.get('saak', ''),
'sask': creds.get('sask', ''),
}
creds = parse_ionapi('/path/to/credentials.ionapi')
Verify: The parsed token_endpoint URL should be a valid HTTPS URL.
For server-to-server integrations, POST to the token endpoint with all four credentials. [src2, src3]
import requests
response = requests.post(creds['token_endpoint'], data={
'grant_type': 'password',
'username': creds['saak'],
'password': creds['sask'],
'client_id': creds['client_id'],
'client_secret': creds['client_secret'],
})
token_data = response.json()
print(f"Token: {token_data['access_token'][:20]}...")
Verify: Response contains access_token and token_type of Bearer. expires_in should be approximately 7200.
Include the access token as a Bearer token in the Authorization header. [src2]
headers = {
'Authorization': f"Bearer {token_data['access_token']}",
'Accept': 'application/json',
}
response = requests.get(
f"{creds['ionapi_url']}/ifsservice/usermgt/v2/users/me",
headers=headers
)
print(response.json())
Verify: GET /ifsservice/usermgt/v2/users/me returns JSON with user details. HTTP 200 confirms auth is working.
If refresh tokens are enabled, exchange the refresh token for a new access token before expiry. Always store the latest rotated refresh token. [src5]
new_tokens = requests.post(creds['token_endpoint'], data={
'grant_type': 'refresh_token',
'refresh_token': token_data['refresh_token'],
'client_id': creds['client_id'],
'client_secret': creds['client_secret'],
}).json()
# CRITICAL: Store the NEW refresh_token -- it rotates every ~8h
current_refresh = new_tokens.get('refresh_token', token_data['refresh_token'])
Verify: New access_token returned with fresh expires_in.
Revoke refresh tokens at the end of integration sessions to prevent orphan grants. [src3]
requests.post(
f"{creds['portal_url']}/revoke_token.oauth2",
data={'token': refresh_token, 'token_type_hint': 'refresh_token'},
auth=(creds['client_id'], creds['client_secret'])
)
Verify: HTTP 200. Subsequent API calls with revoked token return 401.
# Input: .ionapi credentials file path
# Output: Authenticated API call to Infor ION API Gateway
import json, requests, time
class InforAuthClient:
def __init__(self, ionapi_path):
with open(ionapi_path, 'r') as f:
creds = json.load(f)
self.client_id = creds['ci']
self.client_secret = creds['cs']
self.token_url = f"{creds['pu']}/{creds['ot']}"
self.ionapi_url = creds.get('iu', '')
self.saak = creds.get('saak', '')
self.sask = creds.get('sask', '')
self._token = None
self._token_expiry = 0
def get_token(self):
if self._token and time.time() < self._token_expiry - 300:
return self._token
resp = requests.post(self.token_url, data={
'grant_type': 'password',
'username': self.saak, 'password': self.sask,
'client_id': self.client_id, 'client_secret': self.client_secret,
})
resp.raise_for_status()
data = resp.json()
self._token = data['access_token']
self._token_expiry = time.time() + data.get('expires_in', 7200)
return self._token
def call_api(self, endpoint, method='GET', payload=None):
headers = {'Authorization': f'Bearer {self.get_token()}', 'Accept': 'application/json'}
resp = requests.request(method, f'{self.ionapi_url}{endpoint}', headers=headers, json=payload)
resp.raise_for_status()
return resp.json()
client = InforAuthClient('/path/to/credentials.ionapi')
user = client.call_api('/ifsservice/usermgt/v2/users/me')
print(f"Connected as: {user}")
// Input: Path to .ionapi credentials file
// Output: Authenticated API call to ION API Gateway
// npm install axios
const axios = require('axios');
const fs = require('fs');
class InforAuthClient {
constructor(ionapiPath) {
const creds = JSON.parse(fs.readFileSync(ionapiPath, 'utf8'));
this.clientId = creds.ci;
this.clientSecret = creds.cs;
this.tokenUrl = `${creds.pu}/${creds.ot}`;
this.ionapiUrl = creds.iu || '';
this.saak = creds.saak || '';
this.sask = creds.sask || '';
this._token = null;
this._tokenExpiry = 0;
}
async getToken() {
if (this._token && Date.now() < this._tokenExpiry - 300000) return this._token;
const params = new URLSearchParams({
grant_type: 'password', username: this.saak, password: this.sask,
client_id: this.clientId, client_secret: this.clientSecret,
});
const resp = await axios.post(this.tokenUrl, params.toString(),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
this._token = resp.data.access_token;
this._tokenExpiry = Date.now() + (resp.data.expires_in || 7200) * 1000;
return this._token;
}
async callApi(endpoint, method = 'GET', data = null) {
const token = await this.getToken();
const resp = await axios({ method, url: `${this.ionapiUrl}${endpoint}`,
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, data });
return resp.data;
}
}
(async () => {
const client = new InforAuthClient('/path/to/credentials.ionapi');
const user = await client.callApi('/ifsservice/usermgt/v2/users/me');
console.log('Connected as:', user);
})();
# Input: .ionapi file with ci, cs, saak, sask, pu, ot, iu
# Output: Access token + IFS user info
IONAPI_FILE="/path/to/credentials.ionapi"
CI=$(jq -r '.ci' "$IONAPI_FILE")
CS=$(jq -r '.cs' "$IONAPI_FILE")
SAAK=$(jq -r '.saak' "$IONAPI_FILE")
SASK=$(jq -r '.sask' "$IONAPI_FILE")
PU=$(jq -r '.pu' "$IONAPI_FILE")
OT=$(jq -r '.ot' "$IONAPI_FILE")
IU=$(jq -r '.iu' "$IONAPI_FILE")
# Acquire token
TOKEN=$(curl -s -X POST "${PU}/${OT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=${SAAK}&password=${SASK}&client_id=${CI}&client_secret=${CS}" \
| jq -r '.access_token')
# Verify
curl -s "${IU}/ifsservice/usermgt/v2/users/me" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq .
| Field | Full Name | Required For | Type | Description |
|---|---|---|---|---|
ti | Tenant ID | All | String | Identifies the Infor multi-tenant environment |
cn | Common Name | All | String | Display name of the authorized application |
ci | Client ID | All | String | OAuth 2.0 client identifier |
cs | Client Secret | All | String | OAuth 2.0 client secret (sensitive) |
iu | ION API URL | All | URL | Base URL for all API calls after authentication |
pu | Portal URL | All | URL | Base URL for IFS/OAuth endpoints |
oa | OAuth Authorization | Auth Code, Implicit | Path | Authorization endpoint path (append to pu) |
ot | OAuth Token | All | Path | Token endpoint path (append to pu) |
or | OAuth Revoke | All | Path | Token revocation endpoint path |
saak | Service Account Access Key | Backend Service | String | Username equivalent for Resource Owner grant |
sask | Service Account Secret Key | Backend Service | String | Password equivalent for Resource Owner grant |
v | Version | All | String | Credentials file format version |
pu, oa, and ot values vary per tenant. Always construct endpoints from the .ionapi file. [src2, src3]iu (ION API URL) may differ from pu (Portal URL): Do not use pu to construct API call URLs. [src3]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 | Unauthorized | Access token expired, malformed, or revoked | Re-acquire token; check token_endpoint URL matches tenant [src1] |
| 403 | Forbidden / Scope mismatch | Authorized app lacks required scopes, or service account user has insufficient IFS roles | Add required scopes to authorized app; verify IFS user security roles [src2] |
| 400 | Invalid grant | Wrong grant_type, expired/rotated refresh token, or incorrect saak/sask | Verify all 4 credentials from .ionapi file; check refresh token rotation [src3] |
| 429 | Too Many Requests | Exceeded configured quota or spike arrest threshold | Implement exponential backoff; check endpoint policy quotas [src4] |
| 504 | Gateway Timeout | API call exceeded gateway timeout (default 1 min, max 5 min) | Reduce payload/query size; request timeout extension via admin [src7] |
| invalid_grant | Token exchange failure | Refresh token rotated and old token reused, or service account disabled | Use latest rotated refresh_token; verify service account is active [src5] |
invalid_grant. Fix: Always store and use the LATEST refresh_token from each token response. [src5]Implement a keep-alive job using the refresh token at least once every 25 days. [src5]Add *.ionapi to .gitignore. Store in secrets manager. Load at runtime from env vars. [src3]Monitor IFS user account status. Use dedicated integration user excluded from bulk management. [src6]Re-download .ionapi file after scope changes. [src2]# BAD -- endpoint URL hardcoded, breaks when switching tenants
TOKEN_URL = "https://mingle-ionapi.inforcloudsuite.com/TENANT_ID/as/token.oauth2"
response = requests.post(TOKEN_URL, data=payload)
# GOOD -- endpoints parsed from .ionapi file, portable across environments
with open('credentials.ionapi', 'r') as f:
creds = json.load(f)
token_url = f"{creds['pu']}/{creds['ot']}"
response = requests.post(token_url, data=payload)
# BAD -- caches initial refresh_token, ignores rotated token
initial_refresh = token_data['refresh_token']
for batch in batches:
new_tokens = refresh(initial_refresh) # Fails after ~8h
process(batch, new_tokens['access_token'])
# GOOD -- updates refresh_token from every response
current_refresh = token_data['refresh_token']
for batch in batches:
new_tokens = refresh(current_refresh)
current_refresh = new_tokens.get('refresh_token', current_refresh)
save_to_disk(current_refresh)
process(batch, new_tokens['access_token'])
# BAD -- one .ionapi file reused for dev, test, and prod
client = InforAuthClient('shared-credentials.ionapi')
# GOOD -- environment-specific credentials
import os
env = os.environ.get('INFOR_ENV', 'dev')
client = InforAuthClient(f'/secrets/infor-{env}.ionapi')
Always clarify which "IFS" the user means before recommending an auth flow. [src1]Enable "Issue Refresh Token" on every authorized app used for production integrations exceeding 2 hours. [src5]Use pu for auth endpoints, iu for all business API calls. [src3]Configure required scopes during authorized app registration. [src2]Always revoke refresh tokens at end of sessions. Implement shutdown hooks. [src3]Test with the actual service account user from day one. [src6]# Parse .ionapi file and display endpoints (mask secrets)
IONAPI_FILE="/path/to/credentials.ionapi"
jq '{tenant: .ti, name: .cn, portal_url: .pu, ionapi_url: .iu, has_saak: (.saak != null)}' "$IONAPI_FILE"
# Acquire token and display metadata
CI=$(jq -r '.ci' "$IONAPI_FILE"); CS=$(jq -r '.cs' "$IONAPI_FILE")
SAAK=$(jq -r '.saak' "$IONAPI_FILE"); SASK=$(jq -r '.sask' "$IONAPI_FILE")
PU=$(jq -r '.pu' "$IONAPI_FILE"); OT=$(jq -r '.ot' "$IONAPI_FILE")
TOKEN_RESPONSE=$(curl -s -X POST "${PU}/${OT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=${SAAK}&password=${SASK}&client_id=${CI}&client_secret=${CS}")
echo "$TOKEN_RESPONSE" | jq '{token_type, expires_in, scope, has_refresh: (.refresh_token != null)}'
# Test API access
IU=$(jq -r '.iu' "$IONAPI_FILE")
TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
curl -s "${IU}/ifsservice/usermgt/v2/users/me" -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" | jq .
# Check if specific API scope is accessible
curl -s -o /dev/null -w "%{http_code}" "${IU}/YOUR_API_ENDPOINT" -H "Authorization: Bearer $TOKEN"
# 200 = OK, 403 = scope missing
| Version | Release | Status | Key Changes | Notes |
|---|---|---|---|---|
| Infor OS 2026.x | 2026-01 | Current | Refresh token grant lifetime configuration GA | Latest version |
| Infor OS 2025.x | 2025-01 | Supported | Refresh token rotation enabled by default | Breaking: apps caching old refresh tokens fail |
| Infor OS 2024.x | 2024-01 | Supported | Endpoint policies (quota, throttling, cache) | New rate limiting capabilities |
| Infor OS 2023.x | 2023-01 | Legacy | OAuth 2.0 scopes enforcement expanded | More APIs require explicit scope assignment |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building server-to-server integrations with any Infor CloudSuite product via ION API Gateway | Integrating with on-premises Infor OS (ADFS-based) | IFS with ADFS claims-based authentication |
| Need unattended batch/ETL access to Infor Data Lake or CloudSuite APIs | Embedding a widget inside Infor Ming.le that already has SSO | SAML Bearer grant (reuse existing SSO token) |
| Want portable, environment-agnostic auth using .ionapi credential files | Using IFS Cloud from IFS AB (different vendor entirely) | IFS Cloud OAuth 2.0 documentation (IFS AB) |
| Integrating third-party iPaaS (MuleSoft, Boomi, Workato) with Infor | Accessing Infor APIs from within Infor Process Automation (IPA) | IPA internal service mesh (no external OAuth needed) |
expires_in from the token response.