---
# === IDENTITY ===
id: business/erp-integration/infor-authentication/2026
canonical_question: "How does Infor Federated Services / OAuth 2.0 authentication work?"
aliases:
  - "How to authenticate with Infor ION API Gateway using OAuth 2.0"
  - "Infor OS IFS federation hub OAuth token acquisition"
  - "Infor CloudSuite service account backend authentication setup"
  - "Infor .ionapi credentials file format and token management"
entity_type: erp_integration
domain: business > erp-integration > infor-authentication
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Infor CloudSuite (Infor OS / ION API Gateway)"
    vendor: "Infor"
    version: "Infor OS 2025.x / 2026.x"
    edition: "All CloudSuite editions (Industrial, Distribution, Fashion, M3, LN)"
    deployment: "cloud"
    api_surface: "REST (ION API Gateway)"

# === 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: "2025.x -- refresh token rotation enabled by default"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Access tokens expire after approximately 2 hours; refresh tokens rotate every ~8 hours by default"
  - "Backend service (Resource Owner) grant requires four credentials: ClientID, ClientSecret, Service Account AccessKey (saak), Service Account SecretKey (sask)"
  - "ION API Gateway default timeout is 1 minute, extensible to a maximum of 5 minutes"
  - "Buffered policy payload limit is 10 MB; unbuffered has no payload limit"
  - "Refresh tokens idle for >30 days are automatically invalidated"
  - "Authorized applications do NOT issue refresh tokens by default -- must be explicitly enabled"
  - "Scopes in SAML assertion must match API Gateway required scopes or HTTP 403 is returned"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs IFS Cloud (the IFS AB product, not Infor Federated Services) authentication"
    use_instead: "N/A -- IFS Cloud is a different vendor (IFS AB), not Infor"
  - condition: "User needs Infor M3 specific API authentication"
    use_instead: "business/erp-integration/infor-m3-api/2026"
  - condition: "User needs on-premises Infor OS authentication (not multi-tenant cloud)"
    use_instead: "business/erp-integration/infor-on-premise-auth/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: auth_flow
    question: "What authentication flow do you need?"
    type: choice
    options:
      - "backend service (server-to-server, Resource Owner grant -- no user interaction)"
      - "web application (Authorization Code grant -- user login required)"
      - "SAML bearer (already authenticated via Infor Ming.le SSO)"
      - "implicit (single-page app / user-agent based)"
  - key: credential_type
    question: "Which credential format do you have?"
    type: choice
    options:
      - ".ionapi credentials file (downloaded from Infor OS)"
      - "manual ClientID + ClientSecret (registered authorized app)"
      - "SAML assertion from Ming.le federation hub"
  - key: environment
    question: "What is the deployment environment?"
    type: choice
    options:
      - "Infor multi-tenant cloud (CloudSuite)"
      - "Infor single-tenant cloud"
      - "On-premises Infor OS"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/infor-authentication/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/infor-m3-api/2026"
      label: "Infor M3 API capabilities and rate limits"
  solves:
    - id: "business/erp-integration/infor-sap-integration/2026"
      label: "Infor to SAP integration playbook"
  alternative_to:
    - id: "business/erp-integration/infor-on-premise-auth/2026"
      label: "Infor OS on-premises authentication (claims-based / ADFS)"
  often_confused_with:
    - id: "business/erp-integration/ifs-cloud-authentication/2026"
      label: "IFS Cloud authentication (IFS AB -- different vendor, different product)"

# === SOURCES ===
sources:
  - id: src1
    title: "OAuth 2.0 Token Management"
    author: Infor
    url: https://docs.infor.com/inforos/2025.x/en-us/useradminlib_cloud/apigatewayag_cloud/fqj1489507806672.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src2
    title: "How to call an ION API -- Infor Developer Portal Tutorials"
    author: Infor
    url: https://developer.infor.com/tutorials/api-gateway/how-to-call-an-ion-api
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src3
    title: "Infor ION API SDK (GitHub)"
    author: Infor
    url: https://github.com/infor-cloud/ion-api-sdk
    type: official_docs
    published: 2025-03-10
    reliability: high
  - id: src4
    title: "API Gateway Endpoint Policies"
    author: Infor
    url: https://docs.infor.com/inforos/2024.x/en-us/useradminlib_cloud/apigatewaybaasdg/lcs1730257685556.html
    type: official_docs
    published: 2024-11-01
    reliability: authoritative
  - id: src5
    title: "Issuing a refresh token for an authorized application"
    author: Infor
    url: https://docs.infor.com/inforos/2026.x/en-us/useradminlib_cloud/apigatewayag_cloud/hcr1729025141414.html
    type: official_docs
    published: 2026-01-15
    reliability: authoritative
  - id: src6
    title: "Service Authorized App (.ionapi)"
    author: Infor
    url: https://docs.infor.com/inforos/latest/en-us/useradminlib_cloud/etl_dl_ag/oda1623328668051.html
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src7
    title: "ION API Gateway Timeout Limits"
    author: Infor
    url: https://docs.infor.com/inforos/2024.x/en-us/useradminlib_cloud/apigatewayag_cloud/ayc1656493867736.html
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src8
    title: "Strategies, Patterns, and Security Measures for Integrating Infor CloudSuite with AWS"
    author: AWS
    url: https://aws.amazon.com/blogs/apn/strategies-patterns-and-security-measures-for-integrating-infor-cloudsuite-with-aws/
    type: technical_blog
    published: 2024-09-15
    reliability: moderate_high
---

# Infor Authentication: Infor Federated Services (IFS) OAuth 2.0, ION API Gateway, and Service Accounts

## TL;DR

- **Bottom line**: Infor CloudSuite authentication flows through Infor Federated Services (IFS) as the identity/claims provider and the ION API Gateway as the OAuth 2.0 authorization server. For server-to-server integrations, use the Resource Owner grant with a service account; for web apps, use Authorization Code grant; for Ming.le-embedded apps, use SAML Bearer grant. [src1, src2]
- **Key limit**: Access tokens expire after ~2 hours; refresh tokens are NOT issued by default (must be enabled per-app), rotate every ~8 hours when enabled, and auto-invalidate after 30 days idle. [src1, src5]
- **Watch out for**: The .ionapi credentials file contains four separate credentials for backend services (ClientID, ClientSecret, saak, sask) -- losing any one breaks authentication. Unlike most OAuth 2.0 implementations, Infor requires all four for the Resource Owner grant. [src2, src3]
- **Best for**: Any application integrating with Infor CloudSuite products (M3, LN, CloudSuite Industrial, Distribution, Fashion) via the ION API Gateway.
- **Authentication**: OAuth 2.0 via IFS (Infor Federated Services). Four grant types: Authorization Code, Implicit, Resource Owner Password (backend service), SAML Bearer (Ming.le SSO). All API calls require a Bearer token issued by the ION API Gateway. [src1, src3]

## System Profile

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). [src1, src2]

| 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, and others) |
| **Deployment** | Cloud (Infor multi-tenant) |
| **API Docs URL** | [Infor ION API Gateway Administration Guide](https://docs.infor.com/inforos/latest/en-us/useradminlib_cloud/apigatewayag_cloud/qxc1611684793923.html) |
| **Status** | GA |

## API Surfaces & Capabilities

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. [src2]

| 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 [src2] |
| ION Connect (Messaging) | ION messaging | Event-driven, async document exchange | SAML Bearer (via ION) | Async | BODs (Business Object Documents) for ERP events [src2] |
| ION Data Lake | HTTPS/JSON | Analytics, reporting, bulk data export | Backend Service (Resource Owner) | No (batch) | Read-only access to replicated ERP data [src6] |
| 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 [src2] |

## Rate Limits & Quotas

### Per-Request Limits

| 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 (with 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] |

[src7]

### Rolling / Daily Limits

| 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] |

[src4]

## Authentication

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. [src1, src2]

| 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] |

[src1, src2, src3]

### Authentication Gotchas

- **Refresh tokens are OFF by default**: Unlike most OAuth 2.0 implementations, Infor's ION API Gateway does NOT issue refresh tokens unless the "Issue Refresh Token" toggle is explicitly enabled per authorized application. If your integration relies on long-lived sessions, you must configure this. [src5]
- **Four credentials for backend services**: The Resource Owner grant requires ClientID, ClientSecret, Service Account AccessKey (saak), and Service Account SecretKey (sask). This is unique to Infor -- most OAuth 2.0 implementations use only ClientID/ClientSecret for client_credentials. [src2, src3]
- **Scope mismatch returns 403, not 401**: If the scopes in your SAML assertion or authorized app configuration do not match the scopes required by the target API, the ION API Gateway returns HTTP 403 (Forbidden), not 401 (Unauthorized). This misleads debugging -- the token is valid but lacks the required scope. [src2]
- **Refresh token rotation is enabled by default in 2025.x+**: Refresh tokens rotate every ~8 hours. If your application caches the original refresh token instead of storing the latest rotated one, token exchange fails silently. [src5]
- **30-day idle expiry**: Even with infinite grant lifetime configured, a refresh token unused for 30 consecutive days is automatically invalidated. Long-idle integrations must re-authenticate from scratch. [src5]

## Constraints

- **Backend service accounts are user-bound** -- each service account is associated with a specific IFS user, and API calls execute under that user's permissions and security roles. There is no "system" or "admin" service account by default. [src6]
- **ION API Gateway is the only external entry point** -- you cannot bypass the gateway to call Infor CloudSuite APIs directly. All external traffic must route through the gateway's OAuth 2.0 flow. [src2]
- **The .ionapi file is tenant-specific** -- credentials downloaded for one tenant do not work in another. For multi-tenant integrations, you need separate .ionapi files per tenant. [src6]
- **Scope configuration is required per authorized app** -- scopes are not inherited or global. Each authorized app registration must explicitly declare which ION API scopes it needs. [src2]
- **Token endpoint URLs vary by tenant** -- the authorization (`oa`) and token (`ot`) endpoints are tenant-specific URLs embedded in the .ionapi file. Do not hardcode them. [src2, src3]
- **On-premises IFS uses different auth** -- on-premises Infor OS uses IFS with ADFS (claims-based), not OAuth 2.0 via the ION API Gateway. This card covers cloud only. [src1]

## Integration Pattern Decision Tree

```
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 [src2, src3]
|   |   |   |-- NO --> Register Backend Service app in ION API Gateway,
|   |   |       enable "Create Service Account", download .ionapi [src6]
|   |   |-- Need long-running session (>2h)?
|   |       |-- YES --> Enable "Issue Refresh Token" on the authorized app [src5]
|   |       |-- 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) [src2]
|   |   |-- SPA / client-side only
|   |       --> Implicit Grant (register as appropriate type) [src3]
|   |       NOTE: Consider Authorization Code + PKCE if supported
|   |-- App embedded in Infor Ming.le portal
|   |   --> SAML Bearer Grant (reuse SSO token from Ming.le) [src2]
|   |-- Infor Data Lake / ETL extraction
|       --> Backend Service (Resource Owner) with .ionapi file [src6]
|-- Which credential file format?
|   |-- .ionapi file (JSON with ti, cn, ci, cs, iu, pu, oa, ot, saak, sask)
|   |   --> Parse file, extract endpoints and credentials [src3]
|   |-- 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 [src5]
    |-- Idle >30 days --> Must re-authenticate; refresh token auto-invalidated [src5]
```

## Quick Reference

| Operation | Endpoint / Config | Value | Notes |
|---|---|---|---|
| Authorization endpoint | `{pu}/{oa}` | From .ionapi file | Tenant-specific URL [src2] |
| Token endpoint | `{pu}/{ot}` | From .ionapi file | POST with form-encoded params [src2] |
| Token revocation | `{pu}/revoke_token.oauth2` | POST | Send refresh_token + token_type_hint=refresh_token [src3] |
| IFS Service API test | `GET /ifsservice/usermgt/v2/users/me` | Returns current user | Best endpoint to verify auth works [src2] |
| .ionapi portal URL field | `pu` | Base URL for auth endpoints | Tenant-specific, do not hardcode [src3] |
| .ionapi client ID | `ci` | OAuth 2.0 client identifier | From authorized app registration [src3] |
| .ionapi client secret | `cs` | OAuth 2.0 client secret | Treat as sensitive, never commit to VCS [src3] |
| .ionapi service account key | `saak` | Service account access key | Backend Service grant only [src3] |
| .ionapi service account secret | `sask` | Service account secret key | Backend Service grant only [src3] |
| .ionapi tenant ID | `ti` | Infor tenant identifier | Identifies multi-tenant environment [src3] |
| .ionapi ION API URL | `iu` | Base URL for API calls | Target for all API requests after auth [src3] |
| Default access token TTL | ~2 hours | Configurable by admin | Re-request before expiry [src1] |
| Default refresh token rotation | ~8 hours | Configurable | Store latest rotated token [src5] |

## Step-by-Step Integration Guide

### 1. Register an authorized application in ION API Gateway

Navigate to Infor OS > API Gateway > Authorized Apps. Create a new authorized application. Select "Backend Service" for server-to-server integrations or "Web Application" for user-facing apps. For backend services, enable "Create Service Account" and associate it with an IFS user. Download the generated .ionapi credentials file. [src2, src6]

```plaintext
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 (required for scope-restricted APIs)
5. For Web Application:
   a. Select type: "Web Application"
   b. Enter Redirect URL (e.g., https://myapp.example.com/callback)
   c. Enter JavaScript Origins URL
6. Save and download the .ionapi credentials file
7. Store the .ionapi file securely -- it contains all four credentials
```

**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.

### 2. Parse the .ionapi credentials file

The .ionapi file is a JSON file containing all endpoints and credentials needed for authentication. Parse it to extract the token endpoint and credentials. [src3]

```python
# Input:  Path to .ionapi credentials file
# Output: Parsed credentials dictionary

import json

def parse_ionapi(filepath):
    """Parse Infor .ionapi credentials file."""
    with open(filepath, 'r') as f:
        creds = json.load(f)

    # Required fields
    assert 'ci' in creds, "Missing client ID (ci)"
    assert 'cs' in creds, "Missing client secret (cs)"
    assert 'pu' in creds, "Missing portal URL (pu)"
    assert 'ot' in creds, "Missing token endpoint (ot)"

    return {
        'client_id': creds['ci'],
        'client_secret': creds['cs'],
        'portal_url': creds['pu'],
        'token_endpoint': f"{creds['pu']}/{creds['ot']}",
        'auth_endpoint': f"{creds['pu']}/{creds.get('oa', '')}",
        'ionapi_url': creds.get('iu', ''),
        'tenant_id': creds.get('ti', ''),
        # Backend service only
        'saak': creds.get('saak', ''),
        'sask': creds.get('sask', ''),
    }

creds = parse_ionapi('/path/to/credentials.ionapi')
print(f"Token endpoint: {creds['token_endpoint']}")
print(f"ION API URL: {creds['ionapi_url']}")
```

**Verify**: The parsed `token_endpoint` URL should be a valid HTTPS URL. The `ionapi_url` should point to the ION API Gateway base URL for your tenant.

### 3. Acquire access token (Backend Service / Resource Owner grant)

For server-to-server integrations, use the Resource Owner grant with all four credentials from the .ionapi file. Send a POST to the token endpoint with form-encoded parameters. [src2, src3]

```python
# Input:  Parsed .ionapi credentials
# Output: OAuth 2.0 access token

import requests

def get_backend_token(creds):
    """Acquire access token using Resource Owner grant (backend service)."""
    token_url = creds['token_endpoint']

    payload = {
        'grant_type': 'password',
        'username': creds['saak'],       # Service Account Access Key
        'password': creds['sask'],       # Service Account Secret Key
        'client_id': creds['client_id'],
        'client_secret': creds['client_secret'],
    }

    response = requests.post(
        token_url,
        data=payload,
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )
    response.raise_for_status()

    token_data = response.json()
    return token_data  # Contains access_token, token_type, expires_in, etc.

token_data = get_backend_token(creds)
print(f"Access token: {token_data['access_token'][:20]}...")
print(f"Expires in: {token_data.get('expires_in', 'unknown')} seconds")
```

**Verify**: Response contains `access_token` and `token_type` of `Bearer`. The `expires_in` should be approximately 7200 (2 hours).

### 4. Call an ION API with the Bearer token

Include the access token as a Bearer token in the Authorization header for all API calls to the ION API Gateway. [src2]

```python
# Input:  Access token, ION API base URL
# Output: API response

def call_ion_api(token_data, ionapi_url, endpoint):
    """Call an ION API endpoint with Bearer token authentication."""
    headers = {
        'Authorization': f"Bearer {token_data['access_token']}",
        'Accept': 'application/json',
    }

    response = requests.get(
        f"{ionapi_url}{endpoint}",
        headers=headers
    )

    if response.status_code == 403:
        raise PermissionError(
            "HTTP 403 -- check if authorized app has required scopes. "
            "Scope mismatch returns 403, not 401."
        )

    response.raise_for_status()
    return response.json()

# Test: Get current user info
user_info = call_ion_api(
    token_data,
    creds['ionapi_url'],
    '/ifsservice/usermgt/v2/users/me'
)
print(f"Authenticated as: {user_info}")
```

**Verify**: `GET /ifsservice/usermgt/v2/users/me` returns a JSON object with user details for the service account user. A 200 status confirms auth is working.

### 5. Handle token refresh (for long-running processes)

If refresh tokens are enabled on the authorized app, exchange the refresh token for a new access token before the current one expires. Remember to store the rotated refresh token. [src5]

```python
# Input:  Refresh token, parsed credentials
# Output: New access token + new refresh token (rotated)

def refresh_access_token(creds, refresh_token):
    """Exchange refresh token for new access + refresh tokens."""
    token_url = creds['token_endpoint']

    payload = {
        'grant_type': 'refresh_token',
        'refresh_token': refresh_token,
        'client_id': creds['client_id'],
        'client_secret': creds['client_secret'],
    }

    response = requests.post(
        token_url,
        data=payload,
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )
    response.raise_for_status()

    new_tokens = response.json()
    # CRITICAL: Store the NEW refresh_token -- it rotates every ~8h
    # Using the old refresh_token after rotation will FAIL
    return new_tokens

# Usage in long-running process:
import time
while processing:
    if token_near_expiry(token_data):  # Check ~5 min before expiry
        token_data = refresh_access_token(creds, token_data['refresh_token'])
        # Persist new refresh_token to disk/DB for recovery
    do_work(token_data)
```

**Verify**: New `access_token` is returned with fresh `expires_in`. If `refresh_token` is present in response, it is the rotated token -- store it immediately.

### 6. Revoke tokens on completion

Revoke tokens when the integration session ends to prevent orphan grants. Revoking the refresh token also invalidates the associated access token and grant. [src3]

```python
# Input:  Refresh token, parsed credentials
# Output: Token revoked (HTTP 200)

def revoke_token(creds, refresh_token):
    """Revoke refresh token (also invalidates access token and grant)."""
    revoke_url = f"{creds['portal_url']}/revoke_token.oauth2"

    response = requests.post(
        revoke_url,
        data={
            'token': refresh_token,
            'token_type_hint': 'refresh_token',
        },
        auth=(creds['client_id'], creds['client_secret']),
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )
    response.raise_for_status()
    return response.status_code == 200

revoke_token(creds, token_data.get('refresh_token', ''))
print("Token revoked successfully")
```

**Verify**: HTTP 200 response. Subsequent API calls with the revoked token should return 401.

## Code Examples

### Python: Complete Backend Service Authentication

```python
# Input:  .ionapi credentials file path
# Output: Authenticated API call to Infor ION API Gateway

import json
import requests
import time

class InforAuthClient:
    """Infor ION API Gateway OAuth 2.0 client for backend services."""

    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):
        """Get valid access token, refreshing if near expiry."""
        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):
        """Call ION API with automatic token management."""
        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()

# Usage
client = InforAuthClient('/path/to/credentials.ionapi')
user = client.call_api('/ifsservice/usermgt/v2/users/me')
print(f"Connected as: {user}")
```

### JavaScript/Node.js: Backend Service Authentication

```javascript
// 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;
  }
}

// Usage
(async () => {
  const client = new InforAuthClient('/path/to/credentials.ionapi');
  const user = await client.callApi('/ifsservice/usermgt/v2/users/me');
  console.log('Connected as:', user);
})();
```

### cURL: Quick token acquisition and API test

```bash
# Input:  Values from .ionapi file (ci, cs, saak, sask, pu, ot, iu)
# Output: Access token + IFS user info

# Step 1: Parse .ionapi file
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")

# Step 2: Acquire access token (Resource Owner grant)
TOKEN=$(curl -s -X POST "${PU}/${OT}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=${SAAK}" \
  -d "password=${SASK}" \
  -d "client_id=${CI}" \
  -d "client_secret=${CS}" \
  | jq -r '.access_token')

echo "Token acquired: ${TOKEN:0:20}..."

# Step 3: Call IFS Service API to verify
curl -s "${IU}/ifsservice/usermgt/v2/users/me" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" | jq .

# Expected output:
# {
#   "UserName": "service_account_user",
#   "Email": "...",
#   ...
# }
```

## Data Mapping

### .ionapi File Field Reference

| 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 |

[src2, src3, src6]

### Data Type Gotchas

- **Token endpoint URLs are tenant-specific**: The `pu`, `oa`, and `ot` values vary per tenant. Hardcoding any endpoint URL instead of reading from the .ionapi file will break when switching between environments (dev/test/prod). Always construct endpoints from the .ionapi file. [src2, src3]
- **saak and sask are NOT the same as ClientID/ClientSecret**: The .ionapi file contains four credentials (ci, cs, saak, sask). The saak/sask pair acts as the "username/password" in the Resource Owner grant, while ci/cs are the OAuth client credentials. Confusing the two pairs results in authentication failure. [src3]
- **The `iu` (ION API URL) may differ from `pu` (Portal URL)**: The API base URL for making business API calls is different from the portal URL used for OAuth endpoints. Do not use `pu` to construct API call URLs. [src3]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Invalid or expired token | Access token expired, malformed, or revoked | Re-acquire token using credentials; check token_endpoint URL matches tenant [src1] |
| 403 Forbidden | Scope mismatch or insufficient permissions | Authorized app lacks required scopes, or service account user has insufficient IFS roles | Add required scopes to authorized app in ION API Gateway; verify IFS user security roles [src2] |
| 400 Bad Request | Invalid grant | Wrong grant_type, or using expired/wrong refresh token, or incorrect saak/sask | Verify all 4 credentials from .ionapi file; check refresh token has not been rotated [src3] |
| 429 Too Many Requests | Rate limit / quota exceeded | Exceeded configured quota or spike arrest threshold | Implement exponential backoff; check endpoint policy quotas in ION API Gateway [src4] |
| 504 Gateway Timeout | Backend service timeout | API call exceeded gateway timeout (default 1 min, max 5 min) | Reduce payload/query size; request timeout extension via Infor admin [src7] |
| invalid_grant | Token exchange failure | Refresh token rotated and old token reused, or service account disabled | Use the latest rotated refresh_token; verify service account is active in IFS [src5] |

[src1, src2, src4, src7]

### Failure Points in Production

- **Refresh token rotation breaks long-running ETL jobs**: Refresh tokens rotate every ~8 hours by default. If an ETL job caches the initial refresh token and reuses it after rotation, all subsequent token refreshes fail with `invalid_grant`. Fix: `Always store and use the LATEST refresh_token from each token response. Persist it to disk or database after every refresh cycle.` [src5]
- **30-day idle invalidation kills seasonal integrations**: Integrations that run monthly or quarterly lose their refresh tokens after 30 days of inactivity. Fix: `Implement a keep-alive job that uses the refresh token at least once every 25 days. Alternatively, re-authenticate from scratch for infrequent jobs.` [src5]
- **.ionapi file committed to version control**: The .ionapi file contains four plaintext credentials (ci, cs, saak, sask). Committing it to Git exposes all authentication material. Fix: `Add *.ionapi to .gitignore. Store credentials in a secrets manager (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault). Load at runtime from environment variables.` [src3]
- **Service account user disabled or deleted in IFS**: Tokens can still be issued (the OAuth client is valid) but all API calls return 403 because the associated IFS user no longer has permissions. Fix: `Monitor IFS user account status. Set up alerts for user deactivation. Use a dedicated integration user that is excluded from bulk user management operations.` [src6]
- **Scope changes require new .ionapi file**: If you add new scopes to an authorized app after generating the .ionapi file, the existing file continues to work but tokens may lack the new scopes. Fix: `Re-download the .ionapi file after any scope changes. Some scope changes may require regenerating the service account.` [src2]

## Anti-Patterns

### Wrong: Hardcoding token endpoint URLs

```python
# BAD -- endpoint URL hardcoded, breaks when switching tenants/environments
TOKEN_URL = "https://mingle-ionapi.inforcloudsuite.com/TENANT_ID/as/token.oauth2"
response = requests.post(TOKEN_URL, data=payload)
```

### Correct: Read endpoints from .ionapi file

```python
# 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)
```

### Wrong: Ignoring refresh token rotation

```python
# BAD -- caches initial refresh_token, ignores rotated token in response
initial_refresh = token_data['refresh_token']
for batch in batches:
    new_tokens = refresh(initial_refresh)  # Fails after ~8h rotation
    process(batch, new_tokens['access_token'])
```

### Correct: Always store the latest rotated refresh token

```python
# 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)  # Persist for recovery
    process(batch, new_tokens['access_token'])
```

### Wrong: Using same .ionapi file across tenants

```python
# BAD -- one .ionapi file reused for dev, test, and prod tenants
client = InforAuthClient('shared-credentials.ionapi')
# Works in dev, fails in prod because tenant endpoints differ
```

### Correct: Separate .ionapi files per environment

```python
# GOOD -- environment-specific credentials
import os
env = os.environ.get('INFOR_ENV', 'dev')
client = InforAuthClient(f'/secrets/infor-{env}.ionapi')
```

## Common Pitfalls

- **Confusing Infor IFS with IFS Cloud**: "IFS" stands for "Infor Federated Services" in the Infor ecosystem, but "IFS Cloud" is a completely different ERP product from IFS AB (a Swedish company). They share no authentication infrastructure. Agents must disambiguate which "IFS" the user means. [src1]
- **Not enabling refresh tokens before go-live**: Refresh tokens are disabled by default. If you develop and test with short sessions (under 2 hours) and never need refresh, the integration works perfectly -- until production runs a 3-hour batch job and the token expires mid-process. Fix: `Enable "Issue Refresh Token" on every authorized app used for production integrations that may exceed 2 hours.` [src5]
- **Using `pu` instead of `iu` for API calls**: The .ionapi file has two base URLs: `pu` (portal URL for auth endpoints) and `iu` (ION API URL for business API calls). Using the portal URL for API calls returns 404. Fix: `Use pu for auth endpoints (oa, ot, revoke). Use iu for all business API calls.` [src3]
- **Forgetting to associate scopes with the authorized app**: A backend service app with no scopes can get tokens but cannot access scope-restricted APIs. The confusing part: the error is HTTP 403, not a clear "missing scope" message. Fix: `Always configure the required scopes during authorized app registration. Test scope access with a simple API call before building the full integration.` [src2]
- **Not revoking tokens on job completion**: Orphan grants accumulate over time. While they expire eventually, they represent unnecessary security exposure. Fix: `Always revoke refresh tokens at the end of integration sessions. Implement shutdown hooks or finally blocks.` [src3]
- **Testing with admin user, deploying with restricted service account**: The service account user has fewer IFS roles than the admin account used during development. API calls that worked in dev return 403 in prod. Fix: `Test with the actual service account user from day one. Verify all required IFS security roles are assigned.` [src6]

## Diagnostic Commands

```bash
# 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, auth: .oa, token: .ot, has_saak: (.saak != null)}' "$IONAPI_FILE"

# Acquire token and display token 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 (masked):"
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 (replace with target API path)
curl -s -o /dev/null -w "%{http_code}" "${IU}/YOUR_API_ENDPOINT" \
  -H "Authorization: Bearer $TOKEN"
# 200 = access OK, 403 = scope missing or insufficient permissions

# Revoke token (cleanup)
OR=$(jq -r '.or // "revoke_token.oauth2"' "$IONAPI_FILE")
REFRESH=$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token // empty')
if [ -n "$REFRESH" ]; then
  curl -s -X POST "${PU}/${OR}" \
    -u "${CI}:${CS}" \
    -d "token=${REFRESH}&token_type_hint=refresh_token"
  echo "Token revoked"
fi
```

## Version History & Compatibility

| Version | Release | Status | Key Changes | Notes |
|---|---|---|---|---|
| Infor OS 2026.x | 2026-01 | Current | Refresh token grant lifetime configuration GA | Latest documentation version [src5] |
| Infor OS 2025.x | 2025-01 | Supported | Refresh token rotation enabled by default | Breaking: apps that cached old refresh tokens fail [src5] |
| Infor OS 2024.x | 2024-01 | Supported | Endpoint policies (quota, throttling, cache) introduced | New rate limiting capabilities [src4] |
| Infor OS 2023.x | 2023-01 | Legacy | OAuth 2.0 scopes enforcement expanded | More APIs require explicit scope assignment [src2] |
| ION API SDK (GitHub) | Ongoing | Active | Java, .NET, Go client libraries | Community-maintained, updated with OS releases [src3] |

[src1, src4, src5]

### Deprecation Policy

Infor generally provides 12-18 months notice before deprecating authentication flows or making breaking changes to the ION API Gateway. The Implicit Grant is being de-emphasized industry-wide (OAuth 2.1 removes it), and Infor recommends Authorization Code + PKCE for new SPA developments. On-premises IFS configurations with ADFS are supported but receive fewer updates than the cloud ION API Gateway. Always check the Infor OS release notes for your specific CloudSuite version. [src1, src3]

## When to Use / When Not to Use

| 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 an Infor Process Automation (IPA) flow | IPA uses internal service mesh, no external OAuth needed |

## Important Caveats

- Infor Federated Services (IFS) is NOT the same product as IFS Cloud from IFS AB. They share the "IFS" abbreviation but are completely different vendors, products, and authentication stacks. Always clarify which "IFS" the user means before recommending an auth flow. [src1]
- The ION API Gateway rate limits (quota, throttling, spike arrest) are configured per-endpoint by Infor administrators and vary across tenants. The default values (1,000 requests/60s) shown in documentation are examples, not universal defaults. Verify your tenant's actual limits. [src4]
- Service account users in IFS operate under the associated user's full security context. If that user gains or loses roles, the integration's permissions change without any OAuth-level notification. Audit service account user roles regularly. [src6]
- The .ionapi credential file format is Infor-proprietary. There is no industry-standard equivalent. When integrating with iPaaS platforms (MuleSoft, Boomi), you must extract the individual fields (ci, cs, saak, sask, pu, ot) and configure them separately in the iPaaS connector. [src3]
- Access token lifetime (~2h) and refresh token rotation (~8h) are approximate and may be adjusted by tenant administrators. Do not hardcode these values; always check `expires_in` from the token response. [src1, src5]

## Related Units

- [Infor M3 API capabilities and rate limits](/business/erp-integration/infor-m3-api/2026)
- [Infor to SAP integration playbook](/business/erp-integration/infor-sap-integration/2026)
- [Infor OS on-premises authentication (claims-based / ADFS)](/business/erp-integration/infor-on-premise-auth/2026)
