---
# === IDENTITY ===
id: business/erp-integration/infor-data-lake/2026
canonical_question: "How do you extract bulk data from Infor Data Lake using Compass queries?"
aliases:
  - "How to query Infor Data Lake via Compass API for bulk extraction"
  - "Infor Data Fabric Compass v2 API query workflow and pagination"
  - "Infor Data Lake Compass JDBC driver setup and SQL syntax"
  - "Infor ION API Compass query endpoints rate limits and authentication"
entity_type: erp_integration
domain: business > erp-integration > infor-data-lake
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Infor Data Lake (Data Fabric)"
    vendor: "Infor"
    version: "Compass v2 API (2025.x)"
    edition: "Infor OS Cloud (all editions with Data Lake entitlement)"
    deployment: "cloud"
    api_surface: "REST (Compass v2 API via Data Fabric Suite)"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.82
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024-04 — Data Lake API endpoint deprecated, replaced by Data Fabric Compass endpoint"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Maximum page size: 100,000 rows OR 10 MB per result page, whichever is reached first"
  - "Query timeout: 60 minutes maximum — queries exceeding this are terminated and still consume compute time"
  - "Rate limits per tenant per minute: /jobs 100 calls, /status 1,000 calls, /result 10,000 calls, /cancel 1,000 calls"
  - "SELECT queries only — Compass does not support CREATE, INSERT, UPDATE, DELETE, or DROP"
  - "Flat data only via Compass API — supports newline-delimited JSON and DSV formats; hierarchical documents require Data Lake API"
  - "Query results are static snapshots — results do not change when underlying Data Lake data is updated"
  - "Results expire approximately 20 hours after query reaches FINISHED status"
  - "Not available in AWS GovCloud regions"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs to write data INTO Infor Data Lake"
    use_instead: "business/erp-integration/infor-ion-api/2026"
  - condition: "User needs real-time event-driven integration with Infor"
    use_instead: "business/erp-integration/infor-ion-connect/2026"
  - condition: "User needs to query hierarchical/nested document data from Data Lake"
    use_instead: "Use the Data Lake API (non-Compass) which supports all document types"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "batch/bulk (scheduled, high volume)"
      - "ad-hoc query (interactive, on-demand)"
      - "ETL pipeline (regular extraction to external warehouse)"
      - "reporting (BI tool integration via JDBC)"
  - key: data_volume
    question: "What's your expected result set size?"
    type: choice
    options:
      - "< 10,000 records per query"
      - "10,000-100,000 records per query"
      - "> 100,000 records per query (requires pagination)"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "outbound (reading from Infor Data Lake)"
      - "bidirectional (read + ION API for writes)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/infor-data-lake/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-ion-api/2026"
      label: "Infor ION API Gateway — authentication and general API access"
  solves: []
  alternative_to:
    - id: "business/erp-integration/infor-ion-connect/2026"
      label: "Infor ION Connect — event-driven integration alternative"
  often_confused_with:
    - id: "business/erp-integration/infor-ion-api/2026"
      label: "ION API Gateway vs Data Fabric Compass — different API suites for different purposes"

# === SOURCES ===
sources:
  - id: src1
    title: "Using Compass query APIs"
    author: Infor
    url: https://docs.infor.com/inforos/2025.x/en-us/useradminlib_cloud/datafabrug/uan1631199548644.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src2
    title: "Version 2 Compass APIs to page a query result"
    author: Infor
    url: https://docs.infor.com/inforos/latest/en-us/useradminlib_cloud/ioncedg_cloud_osm/bca1622476381846.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src3
    title: "Data Lake Compass queries"
    author: Infor
    url: https://docs.infor.com/inforos/latest/en-us/useradminlib_cloud/datafabrug/hqe1631199553679.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src4
    title: "Query functionality and syntax"
    author: Infor
    url: https://docs.infor.com/inforos/2025.x/en-us/useradminlib_cloud/datafabrug/kei1631199553969.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src5
    title: "Extracting data from Data Lake"
    author: Infor
    url: https://docs.infor.com/inforos/2024.x/en-us/useradminlib_cloud/datafabrug/zkp1631199544812.html
    type: official_docs
    published: 2024-06-15
    reliability: authoritative
  - id: src6
    title: "Querying Infor's Datalake via Compass and ION APIs"
    author: Alistair Johnson
    url: http://movexblog.blogspot.com/2019/11/querying-infors-datalake-via-compass.html
    type: technical_blog
    published: 2019-11-15
    reliability: moderate_high
  - id: src7
    title: "Infor OS Service Limits"
    author: Infor
    url: https://docs.infor.com/inforosulmt/xx/en-us/usagelimits/vaj1720800873922.html
    type: official_docs
    published: 2024-07-15
    reliability: authoritative
  - id: src8
    title: "data-lake-export — Run queries against Infor Data Lake and generate Excel or PDF files"
    author: Billy Willoughby
    url: https://github.com/BillyWilloughby/data-lake-export
    type: community_resource
    published: 2024-01-15
    reliability: moderate
---

# Infor Data Lake: Bulk Data Extraction via Compass Queries

## TL;DR

- **Bottom line**: Use the Compass v2 API (Data Fabric Suite) to submit asynchronous SQL SELECT queries against Infor Data Lake, then paginate through results using offset/limit. The old ION API Suite Compass endpoints are deprecated. [src1]
- **Key limit**: 100,000 rows or 10 MB per result page; 60-minute query timeout; 100 job submissions per tenant per minute. [src1, src2]
- **Watch out for**: Compass only supports SELECT queries on flat data (newline-delimited JSON and DSV). Hierarchical or nested documents require the separate Data Lake API. [src4, src5]
- **Best for**: Scheduled bulk extraction, ETL pipelines, ad-hoc analytics, and BI tool integration (via JDBC driver) against data already published to Infor Data Lake. [src3]
- **Authentication**: OAuth 2.0 via ION API credentials (.ionapi file). Service account access key (saak) and secret key (sask) exchange for bearer tokens with ~2-hour lifetime. [src1, src6]

## System Profile

Infor Data Lake is the centralized data repository within Infor OS (the cloud platform underpinning all Infor CloudSuites). Data from Infor applications — M3, LN, CloudSuite Industrial, CloudSuite Distribution, and others — is automatically published to Data Lake via ION messaging. The Compass query engine sits atop Data Lake and provides SQL-like query capabilities for extracting this data. [src3]

This card covers the Compass v2 REST API available through the Infor Data Fabric Suite, which replaced the deprecated ION API Suite Compass endpoints (removed April 2025). It does NOT cover the Data Lake API (for hierarchical document retrieval), ION Data Lake Flows (for automated S3-based data routing), or the Compass UI within the Data Fabric application. [src1, src5]

| Property | Value |
|---|---|
| **Vendor** | Infor |
| **System** | Infor Data Lake (Data Fabric / Compass v2) |
| **API Surface** | REST (asynchronous job-based) |
| **Current API Version** | v2 (Data Fabric Suite, 2025.x) |
| **Editions Covered** | All Infor OS Cloud editions with Data Lake entitlement |
| **Deployment** | Cloud (Infor multi-tenant) |
| **API Docs URL** | [Infor Data Fabric User Guide](https://docs.infor.com/inforos/2025.x/en-us/useradminlib_cloud/datafabrug/uan1631199548644.html) |
| **Status** | GA (v2 via Data Fabric Suite); v1/v2 via ION API Suite deprecated |

## API Surfaces & Capabilities

Infor provides multiple ways to extract data from Data Lake. The Compass API is the primary programmatic interface for SQL-based querying. [src5]

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Compass v2 API (Data Fabric) | HTTPS/REST | Bulk SQL extraction, ETL | 100,000 rows / 10 MB per page | 100 jobs/min/tenant | No (async) | Yes |
| Compass JDBC Driver | JDBC | BI tools (Tableau, Power BI, DBeaver) | Driver-managed pagination | Shares Compass limits | No | Yes |
| Data Lake API | HTTPS/REST | Hierarchical document retrieval | Limited filtering | ION API limits | Yes | No |
| ION Data Lake Flows | ION Connect | Automated S3 export, CDC delta loads | Full table + deltas | Flow-based | No | Yes |
| Atlas UI | Browser | Interactive data browsing | N/A | N/A | Yes | No |

[src1, src5]

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max rows per result page | 100,000 | Compass v2 /result endpoint | Whichever limit (rows or size) is hit first |
| Max result page size | 10 MB | Compass v2 /result endpoint | Use smaller page sizes for wide tables |
| Query timeout | 60 minutes | All Compass queries | Timed-out queries still consume compute time |
| Result expiration | ~20 hours | Completed query results | Must download before expiry |
| Content-Type header | text/plain | v2 /jobs endpoint | Required for query submission |

[src1, src2]

### Rolling / Daily Limits

| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| POST /v2/compass/jobs | 100 calls | Per minute, per tenant | Shared across all users in tenant |
| POST /v2/compass/{id}/status | 1,000 calls | Per minute, per tenant | — |
| GET /v2/compass/jobs/{id}/result | 10,000 calls | Per minute, per tenant | — |
| PUT /v2/compass/{id}/cancel | 1,000 calls | Per minute, per tenant | — |
| Compute time | Metered | Per tenant | Subject to Infor OS subscription tier |
| Egress | Metered | Per tenant | Charged when data leaves Infor cloud |

[src1, src2, src7]

## Authentication

All Compass API calls go through the ION API Gateway, which uses OAuth 2.0 for authentication. Credentials are provisioned as a `.ionapi` file downloaded from the Infor OS Portal. [src1, src6]

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Resource Owner (Service Account) | Server-to-server, unattended ETL | ~2 hours | Yes (via refresh token) | Use saak/sask from .ionapi file as username/password to obtain bearer token [src6] |
| OAuth 2.0 Authorization Code | User-context, interactive tools | ~2 hours | Yes | Requires redirect URI; used by Compass JDBC and BI tools [src1] |

[src1, src6]

### Authentication Gotchas

- **The .ionapi file contains ALL credentials**: It includes tenant ID (ti), client ID (ci), client secret (cs), service account access key (saak), service account secret key (sask), and all OAuth endpoint URLs. Treat it like a private key — never commit to source control. [src6, src8]
- **Token endpoint is constructed from .ionapi fields**: Combine the `pu` (base URL) and `ot` (token path) fields to form the token endpoint URL. The `saak` goes as username and `sask` as password in the token request. [src6]
- **Tokens expire after ~2 hours**: Always implement token caching and refresh logic. Requesting a new token per API call will hit rate limits quickly. [src6]
- **Compass v2 is in the Data Fabric Suite, not the ION API Suite**: When configuring API Gateway access, ensure the authorized app has access to the Data Fabric API suite, not just the ION API suite. Using the deprecated ION API Suite Compass endpoints will fail. [src1]

## Constraints

- **SELECT only** — Compass does not support CREATE, INSERT, UPDATE, DELETE, or DROP statements. All write operations must use ION messaging or other Infor APIs. [src4]
- **Flat data only** — Compass API can only query newline-delimited JSON and DSV (delimiter-separated values) objects. Hierarchical or nested JSON documents require the Data Lake API. [src5]
- **Results are static snapshots** — Once a query completes, the result set is frozen. New data arriving in Data Lake after query submission is NOT included in results. [src2]
- **60-minute timeout with compute charge** — Timed-out queries are billed for full compute time despite returning no results. Optimize queries or use Data Lake Flows for very large extractions. [src3, src7]
- **No semi-colon multi-query support** — You cannot chain multiple queries in a single submission. Each query requires a separate job submission. [src4]
- **Tenant-scoped isolation** — Query results are only accessible to the tenant and principal ID that submitted the query. No cross-tenant data access. [src1]
- **AWS GovCloud not supported** — Compass API is not available in AWS GovCloud regions. [src1]

## Integration Pattern Decision Tree

```
START — Extract data from Infor Data Lake
├── What data format?
│   ├── Flat (DSV or newline-delimited JSON)
│   │   ├── Data volume < 100K rows per query?
│   │   │   ├── YES → Compass v2 API (single page result)
│   │   │   └── NO → Compass v2 API with pagination (offset/limit)
│   │   ├── Need SQL joins across tables?
│   │   │   ├── YES → Compass v2 API (supports JOINs) [src4]
│   │   │   └── NO → Data Lake API may also work for simple lookups [src5]
│   │   └── Need BI tool integration?
│   │       ├── YES → Compass JDBC driver (DBeaver, Tableau, Power BI) [src3]
│   │       └── NO → Compass v2 REST API
│   └── Hierarchical/nested JSON
│       └── Data Lake API (not Compass) [src5]
├── What's the extraction pattern?
│   ├── One-time or ad-hoc extraction
│   │   └── Compass v2 API or Compass UI [src3]
│   ├── Scheduled recurring ETL
│   │   ├── < 60 min query time?
│   │   │   ├── YES → Compass v2 API with job scheduler
│   │   │   └── NO → ION Data Lake Flows (S3 export) [src5]
│   │   └── Need incremental/delta loads?
│   │       ├── YES → Filter on infor.lastModified() property [src5]
│   │       └── NO → Full table query
│   └── Automated S3 export with CDC
│       └── ION Data Lake Flows (first load = full, subsequent = delta) [src5]
├── Authentication approach?
│   ├── Service account (unattended) → OAuth 2.0 with saak/sask from .ionapi [src6]
│   └── User context (interactive) → OAuth 2.0 authorization code flow
└── Error tolerance?
    ├── Zero-loss → Implement idempotency via infor.DataObjectId tracking
    └── Best-effort → Simple retry on FAILED status
```

## Quick Reference

| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Submit query | POST | /v2/compass/jobs | SQL query as text/plain body | Returns queryID for tracking [src1, src2] |
| Check status | POST | /v2/compass/{queryId}/status | None | Returns RUNNING, FINISHED, FAILED, or CANCELLED [src2] |
| Get results (paginated) | GET | /v2/compass/jobs/{queryId}/result?offset=0&limit=1000 | None | Supports text/csv or application/x-ndjson via resultFormat [src2] |
| Cancel query | PUT | /v2/compass/{queryId}/cancel | None | Stops long-running queries [src2] |
| Get OAuth token | POST | {pu}{ot} (from .ionapi) | grant_type=password&username={saak}&password={sask}&client_id={ci}&client_secret={cs} | Returns access_token (~2h lifetime) [src6] |

## Step-by-Step Integration Guide

### 1. Obtain ION API credentials

Navigate to Infor OS Portal > ION API > Authorized Apps. Create or select a Backend Service authorized app. Click "Download Credentials" and select "Create Service Account" (leave Username blank). Save the `.ionapi` file securely. [src6, src8]

```bash
# The .ionapi file is a JSON file with this structure:
# {
#   "ti": "TENANT_ID",
#   "cn": "common_name",
#   "ci": "client_id",
#   "cs": "client_secret",
#   "iu": "https://mingle-ionapi.inforcloudsuite.com/TENANT_ID",
#   "pu": "https://mingle-sso.inforcloudsuite.com:443/TENANT_ID/as/",
#   "oa": "authorization.oauth2",
#   "ot": "token.oauth2",
#   "or": "revoke_token.oauth2",
#   "saak": "SERVICE_ACCOUNT_ACCESS_KEY",
#   "sask": "SERVICE_ACCOUNT_SECRET_KEY"
# }
```

**Verify**: Open the `.ionapi` file and confirm all fields are populated. The `saak` and `sask` fields must be present for service account authentication.

### 2. Acquire OAuth 2.0 bearer token

Use the token endpoint URL (constructed from `pu` + `ot` in the .ionapi file) with the service account credentials. [src6]

```bash
# Input:  .ionapi file credentials
# Output: OAuth 2.0 access token

TOKEN_URL="https://mingle-sso.inforcloudsuite.com:443/TENANT_ID/as/token.oauth2"

ACCESS_TOKEN=$(curl -s -X POST "$TOKEN_URL" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=SERVICE_ACCOUNT_ACCESS_KEY" \
  -d "password=SERVICE_ACCOUNT_SECRET_KEY" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=CLIENT_SECRET" \
  | jq -r '.access_token')

echo "Token acquired: ${ACCESS_TOKEN:0:20}..."
```

**Verify**: `echo $ACCESS_TOKEN` should output a JWT string. Token should not be null or empty.

### 3. Submit a Compass query

POST your SQL SELECT query to the /v2/compass/jobs endpoint. The Content-Type must be `text/plain`. [src1, src2]

```bash
# Input:  OAuth token, SQL query
# Output: Query ID for status tracking

API_BASE="https://mingle-ionapi.inforcloudsuite.com/TENANT_ID/IONSERVICES/datafabric"

QUERY_ID=$(curl -s -X POST "$API_BASE/v2/compass/jobs" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: text/plain" \
  -d "SELECT CONO, ITNO, ITDS, FUDS FROM MITMAS ORDER BY CONO, ITNO" \
  | jq -r '.queryId')

echo "Query submitted: $QUERY_ID"
```

**Verify**: Response should contain a `queryId` string (UUID format). A null queryId indicates authentication or endpoint issues.

### 4. Poll for query completion

Check the status endpoint until the query reaches FINISHED, FAILED, or CANCELLED state. [src2]

```bash
# Input:  Query ID, OAuth token
# Output: Query status and row count

while true; do
  STATUS_RESPONSE=$(curl -s -X POST "$API_BASE/v2/compass/$QUERY_ID/status" \
    -H "Authorization: Bearer $ACCESS_TOKEN")

  STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status')
  echo "Status: $STATUS"

  if [ "$STATUS" = "FINISHED" ]; then
    ROW_COUNT=$(echo "$STATUS_RESPONSE" | jq -r '.rowCount')
    echo "Query complete. Rows: $ROW_COUNT"
    break
  elif [ "$STATUS" = "FAILED" ] || [ "$STATUS" = "CANCELLED" ]; then
    echo "Query failed/cancelled. Check result endpoint for error details."
    break
  fi

  sleep 5  # Poll every 5 seconds
done
```

**Verify**: Status transitions from RUNNING to FINISHED. The `rowCount` field shows total available rows.

### 5. Retrieve paginated results

Use offset and limit parameters to page through the result set. Maximum 100,000 rows or 10 MB per page. [src2]

```bash
# Input:  Query ID, page parameters
# Output: CSV or NDJSON result data

PAGE_SIZE=10000
OFFSET=0

# Download first page as CSV
curl -s "$API_BASE/v2/compass/jobs/$QUERY_ID/result?offset=$OFFSET&limit=$PAGE_SIZE" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Accept: text/csv" \
  -H "Accept-Encoding: gzip" \
  -o "result_page_0.csv"

echo "Downloaded page 0: $(wc -l < result_page_0.csv) lines"

# For NDJSON format instead:
# -H "Accept: application/x-ndjson"
```

**Verify**: Output file contains header row + data rows. Row count should match expected page size or remaining rows.

### 6. Implement full pagination loop

For result sets larger than one page, iterate with increasing offsets. [src2]

```bash
# Input:  Query ID, total row count
# Output: All result pages downloaded

PAGE_SIZE=50000
TOTAL_ROWS=$ROW_COUNT
OFFSET=0
PAGE=0

while [ $OFFSET -lt $TOTAL_ROWS ]; do
  curl -s "$API_BASE/v2/compass/jobs/$QUERY_ID/result?offset=$OFFSET&limit=$PAGE_SIZE" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Accept: text/csv" \
    -H "Accept-Encoding: gzip" \
    -o "result_page_${PAGE}.csv"

  echo "Page $PAGE downloaded (offset=$OFFSET, limit=$PAGE_SIZE)"
  OFFSET=$((OFFSET + PAGE_SIZE))
  PAGE=$((PAGE + 1))
done

echo "All $PAGE pages downloaded."
```

**Verify**: Total rows across all pages equals the `rowCount` from the status response.

## Code Examples

### Python: Bulk extract with pagination

```python
# Input:  .ionapi credentials file path, SQL query
# Output: All query results as a pandas DataFrame

import json
import time
import requests
import pandas as pd
from io import StringIO

def load_ionapi_credentials(filepath):
    """Load credentials from .ionapi file."""
    with open(filepath, 'r') as f:
        return json.load(f)

def get_access_token(creds):
    """Acquire OAuth 2.0 token using service account credentials."""
    token_url = f"{creds['pu']}{creds['ot']}"
    response = requests.post(token_url, data={
        'grant_type': 'password',
        'username': creds['saak'],
        'password': creds['sask'],
        'client_id': creds['ci'],
        'client_secret': creds['cs']
    })
    response.raise_for_status()
    return response.json()['access_token']

def compass_query(creds, sql, page_size=50000):
    """Submit Compass query and return all results as DataFrame."""
    token = get_access_token(creds)
    base_url = f"{creds['iu']}/IONSERVICES/datafabric"
    headers = {'Authorization': f'Bearer {token}'}

    # Step 1: Submit query
    resp = requests.post(
        f"{base_url}/v2/compass/jobs",
        headers={**headers, 'Content-Type': 'text/plain'},
        data=sql
    )
    resp.raise_for_status()
    query_id = resp.json()['queryId']
    print(f"Query submitted: {query_id}")

    # Step 2: Poll for completion
    while True:
        status_resp = requests.post(
            f"{base_url}/v2/compass/{query_id}/status",
            headers=headers
        )
        status_resp.raise_for_status()
        status_data = status_resp.json()
        status = status_data['status']

        if status == 'FINISHED':
            row_count = status_data.get('rowCount', 0)
            print(f"Query finished. Total rows: {row_count}")
            break
        elif status in ('FAILED', 'CANCELLED'):
            raise RuntimeError(f"Query {status}: {status_data}")
        time.sleep(3)

    # Step 3: Paginate and collect results
    all_dfs = []
    offset = 0
    while offset < row_count:
        result_resp = requests.get(
            f"{base_url}/v2/compass/jobs/{query_id}/result",
            headers={**headers, 'Accept': 'text/csv'},
            params={'offset': offset, 'limit': page_size}
        )
        result_resp.raise_for_status()
        df_page = pd.read_csv(StringIO(result_resp.text))
        all_dfs.append(df_page)
        offset += page_size
        print(f"  Retrieved {len(df_page)} rows (offset={offset - page_size})")

    return pd.concat(all_dfs, ignore_index=True) if all_dfs else pd.DataFrame()

# Usage
creds = load_ionapi_credentials('my_credentials.ionapi')
df = compass_query(creds, "SELECT CONO, ITNO, ITDS FROM MITMAS ORDER BY ITNO")
print(f"Total records extracted: {len(df)}")
```

### JavaScript/Node.js: Compass query with async/await

```javascript
// Input:  .ionapi file path, SQL query string
// Output: Array of result objects from Data Lake

// npm install axios@1
const axios = require('axios');
const fs = require('fs');

async function compassQuery(ionapiPath, sql, pageSize = 50000) {
  const creds = JSON.parse(fs.readFileSync(ionapiPath, 'utf-8'));

  // Step 1: Get OAuth token
  const tokenUrl = `${creds.pu}${creds.ot}`;
  const tokenResp = await axios.post(tokenUrl, new URLSearchParams({
    grant_type: 'password',
    username: creds.saak,
    password: creds.sask,
    client_id: creds.ci,
    client_secret: creds.cs,
  }));
  const token = tokenResp.data.access_token;
  const baseUrl = `${creds.iu}/IONSERVICES/datafabric`;
  const headers = { Authorization: `Bearer ${token}` };

  // Step 2: Submit query
  const jobResp = await axios.post(`${baseUrl}/v2/compass/jobs`, sql, {
    headers: { ...headers, 'Content-Type': 'text/plain' },
  });
  const queryId = jobResp.data.queryId;
  console.log(`Query submitted: ${queryId}`);

  // Step 3: Poll for completion
  let status = 'RUNNING';
  let rowCount = 0;
  while (status === 'RUNNING') {
    await new Promise((r) => setTimeout(r, 3000));
    const statusResp = await axios.post(
      `${baseUrl}/v2/compass/${queryId}/status`,
      null,
      { headers }
    );
    status = statusResp.data.status;
    rowCount = statusResp.data.rowCount || 0;
  }

  if (status !== 'FINISHED') {
    throw new Error(`Query ${status}`);
  }
  console.log(`Query finished. Rows: ${rowCount}`);

  // Step 4: Paginate results (NDJSON format)
  const allRows = [];
  let offset = 0;
  while (offset < rowCount) {
    const resultResp = await axios.get(
      `${baseUrl}/v2/compass/jobs/${queryId}/result`,
      {
        headers: { ...headers, Accept: 'application/x-ndjson' },
        params: { offset, limit: pageSize },
      }
    );
    const lines = resultResp.data.trim().split('\n');
    const rows = lines.map((line) => JSON.parse(line));
    allRows.push(...rows);
    offset += pageSize;
  }

  return allRows;
}

// Usage
compassQuery('./my_credentials.ionapi', 'SELECT TOP 100 CONO, ITNO FROM MITMAS')
  .then((rows) => console.log(`Extracted ${rows.length} records`))
  .catch(console.error);
```

### cURL: Quick Compass API test

```bash
# Input:  .ionapi credentials, SQL query
# Output: First page of query results

# Step 1: Get token
TOKEN=$(curl -s -X POST \
  "https://mingle-sso.inforcloudsuite.com:443/TENANT/as/token.oauth2" \
  -d "grant_type=password&username=SAAK&password=SASK&client_id=CI&client_secret=CS" \
  | jq -r '.access_token')

# Step 2: Submit query
BASE="https://mingle-ionapi.inforcloudsuite.com/TENANT/IONSERVICES/datafabric"
QID=$(curl -s -X POST "$BASE/v2/compass/jobs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  -d "SELECT TOP 10 CONO, ITNO, ITDS FROM MITMAS" \
  | jq -r '.queryId')

# Step 3: Wait and check status
sleep 10
curl -s -X POST "$BASE/v2/compass/$QID/status" \
  -H "Authorization: Bearer $TOKEN" | jq .

# Step 4: Get results as CSV
curl -s "$BASE/v2/compass/jobs/$QID/result?offset=0&limit=100" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: text/csv"
```

## Data Mapping

### Infor Data Lake Property Reference

| Data Lake Property | SQL Name | Type | Purpose | Gotcha |
|---|---|---|---|---|
| infor.DataObjectId | dl_id | String | Unique identifier per data object in Data Lake | Not the same as the source system record ID [src5] |
| infor.lastModified() | dl_document_indexed_date | Timestamp | When data object was added/updated in Data Lake | Use for incremental loads; was named dl_document_date before 2023.02 [src5] |
| infor.DataObjectSeqId | N/A | Integer | Record/line number within a data object | Useful for deduplication in multi-line objects |
| Source system fields | Original names | Varies | Business data fields (e.g., CONO, ITNO from M3) | Field names are ERP-specific — M3 uses cryptic 4-5 char names |

[src5]

### Data Type Gotchas

- **Field names are ERP-specific**: Infor M3 uses short cryptic field names (e.g., ITNO = Item Number, CONO = Company, FUDS = Full Description). CloudSuite Industrial uses different naming conventions. There is no standard cross-product naming. [src6]
- **Timestamps in Data Lake may differ from source**: The `infor.lastModified()` timestamp reflects when data arrived in Data Lake, NOT when the source record was last modified. Time lag depends on ION publishing frequency. [src5]
- **DSV format varies by data source**: Different Infor products publish to Data Lake in different formats (some as JSON, some as DSV). The Compass query engine normalizes this, but raw Data Lake API access sees the original format. [src5]
- **NULL handling**: Empty values in DSV data may appear as empty strings rather than SQL NULL. Use COALESCE or explicit null checks in queries. [src4]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| FAILED (job status) | Query execution error | Syntax error, invalid table/field, timeout | Retrieve error details from /result endpoint; fix query syntax [src2] |
| 401 Unauthorized | Invalid or expired token | OAuth token expired (~2h) or invalid | Refresh token or request new one using .ionapi credentials [src6] |
| 429 Too Many Requests | Rate limit exceeded | Exceeded per-minute API call quota | Implement exponential backoff; reduce polling frequency [src1] |
| Process ERROR 401 | Invalid property name | Referencing non-existent column/field | Verify field names using Atlas UI or Data Catalog [src4] |
| INTERNAL ERROR 624 | Internal Compass error | Infrastructure issue | Contact Infor Support with full error message [src3] |
| Timeout (60 min) | Query exceeded time limit | Query too complex or too much data scanned | Optimize query with WHERE clauses; use LIMIT; consider Data Lake Flows for very large datasets [src3, src7] |

[src1, src2, src3]

### Failure Points in Production

- **Token expiry during long pagination**: A query with millions of rows may take longer to paginate than the ~2-hour token lifetime. Fix: `Check token expiry before each page request. Implement proactive token refresh when remaining lifetime is < 10 minutes.` [src6]
- **60-minute timeout on complex queries**: Large table scans or cross-table JOINs without WHERE clauses can easily exceed the timeout, and compute time is still billed. Fix: `Always include WHERE clauses to limit data scanned. Use infor.lastModified() for incremental extraction. Test query timing in Compass UI before API automation.` [src3, src7]
- **Results expire after ~20 hours**: If you submit a query but don't retrieve results within 20 hours, the data is purged. Fix: `Start result retrieval immediately after FINISHED status. Do not use Compass as a data caching layer.` [src2]
- **Deprecated ION API Suite endpoints**: Code using the old ION API Suite Compass endpoints (deprecated April 2025) will fail. Fix: `Migrate to Data Fabric Suite endpoints. Change base URL from /IONSERVICES/datalakeapi to /IONSERVICES/datafabric.` [src1]
- **.ionapi file credential rotation**: When ION API credentials are regenerated (e.g., by an admin), all integrations using the old .ionapi file break silently. Fix: `Implement monitoring for 401 errors. Store .ionapi contents in a secrets manager with rotation alerts.` [src6]

## Anti-Patterns

### Wrong: Polling status in a tight loop

```python
# BAD — burns through 1,000/min status rate limit in seconds
while True:
    status = check_status(query_id)
    if status == 'FINISHED':
        break
```

### Correct: Poll with exponential backoff

```python
# GOOD — respects rate limits, starts fast and backs off
import time

delay = 2  # initial delay in seconds
max_delay = 30
while True:
    status = check_status(query_id)
    if status in ('FINISHED', 'FAILED', 'CANCELLED'):
        break
    time.sleep(delay)
    delay = min(delay * 1.5, max_delay)
```

### Wrong: Requesting a new OAuth token for every API call

```python
# BAD — wastes time, may trigger auth server rate limits
for page in range(total_pages):
    token = get_access_token(creds)  # new token per page!
    get_results(token, query_id, offset=page * page_size)
```

### Correct: Cache token and reuse until near expiry

```python
# GOOD — token cached, only refreshed when near expiry
token = get_access_token(creds)
token_expiry = time.time() + 7000  # ~2h minus buffer

for page in range(total_pages):
    if time.time() > token_expiry:
        token = get_access_token(creds)
        token_expiry = time.time() + 7000
    get_results(token, query_id, offset=page * page_size)
```

### Wrong: Extracting full tables without WHERE clause for incremental loads

```sql
-- BAD — scans entire table every time, may timeout
SELECT * FROM MITMAS ORDER BY ITNO
```

### Correct: Use infor.lastModified() for incremental extraction

```sql
-- GOOD — only extracts records added/modified since last run
SELECT CONO, ITNO, ITDS, FUDS FROM MITMAS
WHERE dl_document_indexed_date > '2026-02-28T00:00:00Z'
ORDER BY ITNO
```

## Common Pitfalls

- **Using the deprecated ION API Suite Compass endpoint**: The old endpoint at `/IONSERVICES/datalakeapi` was removed April 2025. Fix: `Migrate to the Data Fabric Suite endpoint at /IONSERVICES/datafabric. Update all base URLs in your integration code.` [src1]
- **Forgetting Content-Type: text/plain on job submission**: The v2 /jobs endpoint requires `text/plain` Content-Type for the SQL body. Sending `application/json` will fail silently or return a 400 error. Fix: `Always set Content-Type: text/plain when POSTing to /v2/compass/jobs.` [src1]
- **Assuming real-time data**: Data Lake receives data via ION messaging, which introduces latency (minutes to hours depending on configuration). Fix: `For real-time data needs, use ION API direct calls to the source ERP, not Data Lake queries.` [src5]
- **Not handling the FAILED status from /result**: When a query fails, the error details are in the /result response, not the /status response. Fix: `Always call /result even for FAILED queries to retrieve the error message and root cause.` [src2]
- **Ignoring compute time metering**: Every query consumes metered compute time, including timed-out queries. Running unnecessary or poorly optimized queries accumulates costs. Fix: `Test queries in Compass UI first. Add WHERE clauses. Use LIMIT during development. Monitor compute time usage in Infor OS admin.` [src7]
- **Page size too large for wide tables**: Requesting 100,000 rows on a table with 50+ columns may exceed the 10 MB page limit, returning fewer rows than requested. Fix: `Select only needed columns. Start with a smaller page size (e.g., 10,000) and adjust based on actual response sizes.` [src2]

## Diagnostic Commands

```bash
# Test OAuth token acquisition
curl -s -X POST "https://mingle-sso.inforcloudsuite.com:443/TENANT/as/token.oauth2" \
  -d "grant_type=password&username=SAAK&password=SASK&client_id=CI&client_secret=CS" \
  | jq '{access_token: .access_token[:20], expires_in: .expires_in, token_type: .token_type}'

# Submit a minimal test query
curl -s -X POST "$BASE/v2/compass/jobs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  -d "SELECT TOP 1 * FROM MITMAS" | jq .

# Check query status
curl -s -X POST "$BASE/v2/compass/$QID/status" \
  -H "Authorization: Bearer $TOKEN" | jq .

# Cancel a running query
curl -s -X PUT "$BASE/v2/compass/$QID/cancel" \
  -H "Authorization: Bearer $TOKEN" | jq .

# List available tables (via Data Catalog if available)
# Note: Table discovery typically requires Compass UI or Atlas UI
# API-based table listing depends on your Data Fabric configuration

# Check JDBC connectivity
# java -cp compass-jdbc-driver.jar com.infor.idl.jdbc.Driver \
#   "jdbc:infordatalake://TENANT?ionApiCredentials=ENCODED_CREDENTIALS"
```

## Version History & Compatibility

| Version | Release | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| Compass v2 (Data Fabric Suite) | 2024 | Current | N/A | Recommended endpoint for all new integrations [src1] |
| Compass v2 (ION API Suite) | 2022 | Deprecated (removed Apr 2025) | Base URL changed | Migrate from /datalakeapi to /datafabric [src1] |
| Compass v1 (ION API Suite) | 2019 | Deprecated (removed Apr 2025) | No pagination support | Migrate to v2 for pagination and better rate limits [src1, src6] |
| JDBC Connection String Auth | 2021-06 | Current | N/A | Simplified authentication vs file-based approach [src3] |

[src1, src2]

### Deprecation Policy

Infor deprecates API endpoints with advance notice through release notes and Documentation Central. The Compass v1/v2 ION API Suite endpoints were deprecated with a migration window of approximately 12-18 months before removal. Always monitor Infor's release notes and Data Fabric User Guide for upcoming changes. [src1]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Extracting bulk data from Infor ERP products (M3, LN, CloudSuite) that publish to Data Lake | Need to write data back to Infor applications | ION API or BOD-based integration |
| Building ETL pipelines to external data warehouses (Snowflake, Databricks, BigQuery) | Need real-time (<1 second) data from Infor | Direct ION API calls to source ERP |
| Running SQL joins across multiple Infor data objects in Data Lake | Need to query hierarchical/nested JSON documents | Data Lake API (non-Compass) |
| BI tool connectivity (Tableau, Power BI) via Compass JDBC driver | Data volume requires >60 minutes of query time | ION Data Lake Flows (S3 export) |
| Ad-hoc data analysis and exploration | Need guaranteed delivery with CDC/event-driven patterns | ION Connect with Business Events |

## Important Caveats

- Compass API is limited to SELECT queries on flat data (DSV and newline-delimited JSON). It does NOT support DML statements or hierarchical document querying. For writes, use ION messaging. For nested documents, use the Data Lake API. [src4, src5]
- The 60-minute query timeout is a hard limit. Timed-out queries still consume compute time. Always test query performance in the Compass UI before automating via API. [src3, src7]
- Data in the Data Lake may not be real-time. Latency depends on ION publishing configuration and can range from minutes to hours. Do not use Data Lake for time-critical integrations. [src5]
- Rate limits are per-tenant, not per-user. Heavy API usage by one integration can impact all other integrations in the same tenant. Coordinate with other teams sharing the tenant. [src1, src2]
- Compute time and egress are metered and may incur additional costs depending on your Infor OS subscription tier. Monitor usage to avoid unexpected charges. [src7]

## Related Units

- [Infor ION API Gateway](/business/erp-integration/infor-ion-api/2026)
- [Infor ION Connect — event-driven integration](/business/erp-integration/infor-ion-connect/2026)
