---
# === IDENTITY ===
id: business/erp-integration/infor-idm-api/2026
canonical_question: "What are the Infor IDM document management API capabilities and constraints?"
aliases:
  - "Infor Document Management REST API endpoints and limits"
  - "How to upload and download documents via Infor IDM API"
  - "Infor IDM items endpoint POST body format and file size limits"
  - "Infor OS Document Management API integration patterns"
entity_type: erp_integration
domain: business > erp-integration > infor-idm-api
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Infor Document Management (IDM)"
    vendor: "Infor"
    version: "Infor OS 2024.x / 2025.x"
    edition: "Essentials, Professional, Enterprise"
    deployment: "cloud"
    api_surface: "REST (via ION API Gateway)"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.78
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024.x — large file API threshold configurable"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Maximum file upload size is 2 GB per document (global system limit)"
  - "Copy Document feature limited to 50 MB maximum"
  - "Upload times out after 30 minutes for any single operation"
  - "Files larger than 20 MB default threshold are uploaded as background actions"
  - "Document retrievals capped at 75K-1.5M/day depending on subscription tier (peak: 1K-2.5K/min)"
  - "Document processing capped at 75K-1.5M/day depending on subscription tier (peak: 600-2K/min)"
  - "Searches capped at 75K-1.5M/day depending on subscription tier (peak: 1K-2.5K/min)"
  - "Search responses include base64-encoded file content by default — no field-level projection support"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs ForgeRock IDM (PingIDM) identity management API"
    use_instead: "N/A — different product entirely despite similar name"
  - condition: "User needs Infor M3 or LN application-specific API"
    use_instead: "business/erp-integration/infor-m3-api/2026"
  - condition: "User needs Infor ION API Gateway authentication only"
    use_instead: "business/erp-integration/infor-ion-api-authentication/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "document upload (single file with metadata)"
      - "document search (XQuery by attributes)"
      - "document download (retrieve file content by ID)"
      - "batch import (bulk file loading via IDM Utilities)"
  - key: data_volume
    question: "What's your daily document volume?"
    type: choice
    options:
      - "< 1,000 documents/day"
      - "1,000-75,000 documents/day"
      - "> 75,000 documents/day"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "inbound (uploading documents to IDM)"
      - "outbound (downloading/searching documents from IDM)"
      - "bidirectional (upload and retrieve)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/infor-idm-api/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "business/erp-integration/infor-ion-api-authentication/2026"
      label: "Infor ION API Gateway OAuth 2.0 authentication (required for all IDM API calls)"
  related_to:
    - id: "business/erp-integration/infor-m3-api/2026"
      label: "Infor M3 API capabilities (often integrated with IDM for document attachment)"
  solves:
    - id: "business/erp-integration/erp-document-management-integration/2026"
      label: "Cross-ERP document management integration playbook"
  alternative_to:
    - id: "business/erp-integration/sharepoint-api/2026"
      label: "SharePoint document management API (alternative ECM platform)"
  often_confused_with:
    - id: "business/erp-integration/forgerock-idm-api/2026"
      label: "ForgeRock/PingIDM identity management API (different product, similar acronym)"

# === SOURCES ===
sources:
  - id: src1
    title: "Infor OS Service Limits — Document Management"
    author: Infor
    url: https://docs.infor.com/inforosulmt/xx/en-us/usagelimits/wyu1720800865169.html
    type: official_docs
    published: 2024-07-12
    reliability: authoritative
  - id: src2
    title: "IDM REST API Documentation"
    author: Infor
    url: https://docs.infor.com/inforos/2023.x/en-us/useradminlib_onpremises/inforosag_on-premise/bzt1521627745232.html
    type: official_docs
    published: 2023-06-15
    reliability: authoritative
  - id: src3
    title: "How to Use IDM's APIs — Infor Developer Portal"
    author: Infor
    url: https://developer.infor.com/tutorials/document-management/how-to-use-idms-apis
    type: official_docs
    published: 2024-01-10
    reliability: authoritative
  - id: src4
    title: "Upload a Document to Infor IDM Using Infor ION"
    author: Warnajith Hettiarachchi
    url: http://warnajith.blogspot.com/2022/12/upload-document-to-infor-idm-using.html
    type: technical_blog
    published: 2022-12-15
    reliability: moderate_high
  - id: src5
    title: "How to Call an ION API — Infor Developer Portal"
    author: Infor
    url: https://developer.infor.com/tutorials/api-gateway/how-to-call-an-ion-api
    type: official_docs
    published: 2024-03-20
    reliability: authoritative
  - id: src6
    title: "Infor ION API Gateway SDK (GitHub)"
    author: Infor
    url: https://github.com/infor-cloud/ion-api-sdk
    type: community_resource
    published: 2024-06-10
    reliability: high
  - id: src7
    title: "IDM API Items/Search Endpoint Discussion"
    author: Infor Community
    url: https://community.infor.com/discussion/26026/idm-api-infor-idm-api-items-search
    type: community_resource
    published: 2023-09-15
    reliability: moderate_high
  - id: src8
    title: "ION API OAuth2 Resource Owner Grant Example"
    author: Infor
    url: https://docs.infor.com/inforos/2024.x/en-us/useradminlib_cloud/apigatewayag_cloud/ffu1514559537410.html
    type: official_docs
    published: 2024-05-20
    reliability: authoritative
---

# Infor IDM Document Management API: Capabilities, Endpoints, and Constraints

## TL;DR

- **Bottom line**: Infor IDM exposes a REST API through the ION API Gateway for document CRUD, search (via XQuery), and file upload/download. All calls require OAuth 2.0 bearer tokens via the ION API Gateway. Upload uses base64-encoded file content in JSON/XML payloads to `POST /IDM/api/items`.
- **Key limit**: Maximum file size is 2 GB per document; daily throughput caps range from 75,000 to 1.5M document operations depending on subscription tier (Essentials/Professional/Enterprise). [src1]
- **Watch out for**: Search responses include base64-encoded file content by default — a query returning 1,000 documents will attempt to return all file contents inline, creating enormous payloads. Always use separate `/items/{pid}/resource/stream` calls for file retrieval. [src7]
- **Best for**: Document lifecycle management (upload, version, search, download) within Infor CloudSuite ecosystems integrated with M3, LN, or other Infor ERP applications.
- **Authentication**: OAuth 2.0 via ION API Gateway using Resource Owner grant (service account accessKey/secretKey) for backend integrations; Authorization Code grant for interactive apps. [src5, src8]

## System Profile

Infor Document Management (IDM) is a cloud-based enterprise content management (ECM) system that is part of the Infor OS platform. It provides document storage, versioning, search, and lifecycle management capabilities for all Infor CloudSuite applications (M3, LN, Syteline, etc.). IDM is accessed exclusively through the Infor ION API Gateway — there is no direct API endpoint outside the gateway. This card covers the cloud deployment; on-premises Infor OS deployments share the same REST API surface but have different infrastructure constraints. [src2, src3]

| Property | Value |
|---|---|
| **Vendor** | Infor |
| **System** | Infor Document Management (IDM), Infor OS 2024.x/2025.x |
| **API Surface** | REST (JSON/XML) via ION API Gateway |
| **Current API Version** | IDM API v1 (no formal versioning; tied to Infor OS release) |
| **Editions Covered** | Essentials (15K docs), Professional (75K docs), Enterprise (300K docs) |
| **Deployment** | Cloud (Infor CloudSuite) |
| **API Docs URL** | [Infor Developer Portal — Document Management](https://developer.infor.com/tutorials/document-management) |
| **Status** | GA |

## API Surfaces & Capabilities

IDM exposes a single REST API surface through the ION API Gateway. All endpoints are prefixed with the tenant-specific gateway URL. The API is organized around the Items resource (documents). [src2, src3]

| API Surface | Protocol | Best For | Max Payload | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| IDM REST API (Items) | HTTPS/JSON or XML | Document CRUD, metadata management | 2 GB per file (base64) | Tier-dependent: 75K-1.5M ops/day | Yes | No (single item per request) |
| IDM Items Search | HTTPS/JSON or XML | Document discovery via XQuery | Response can be very large | Tier-dependent: 75K-1.5M searches/day | Yes | No |
| IDM Resource Stream | HTTPS/binary | File content download | 2 GB | Shared with retrieval quota | Yes | No |
| IDM Utilities (Batch) | CLI/GUI tool | Bulk import, export, batch update | N/A (tool-based) | Not API-rate-limited | No (batch) | Yes |
| ION Document Flows | ION Process Designer | Event-driven document routing | Per-BOD limits | ION message limits | Event-driven | Yes (via workflow) |

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max file size per upload | 2 GB | POST /IDM/api/items | Global system limit; base64 encoding increases payload by ~33% [src1] |
| Max file size for Copy Document | 50 MB | Copy operations only | Much lower than standard upload [src1] |
| Upload timeout | 30 minutes | All upload operations | Connection drops if exceeded [src1] |
| Background upload threshold | 20 MB (default, configurable) | Large file uploads | Files above threshold are processed asynchronously [src1] |
| Large file API threshold | 2 MB (default, configurable) | Resource stream | Files above this use the optimized large file API [src2] |
| Search pagination | offset + limit params | /IDM/api/items/search | No server-side cursor; offset-based only [src7] |

[src1, src2]

### Rolling / Daily Limits

| Limit Type | Essentials (1-74,999) | Professional (75K-299,999) | Enterprise (300K+) | Peak Rate |
|---|---|---|---|---|
| Document retrievals | 75,000/day | 750,000/day | 1,500,000/day | 1,000-2,500/min |
| Document processing (upload/update) | 75,000/day | 750,000/day | 1,500,000/day | 600-2,000/min |
| Searches | 75,000/day | 750,000/day | 1,500,000/day | 1,000-2,500/min |

Expansion available via ION-S-DM add-on SKU in 15,000-unit increments. [src1]

## Authentication

All IDM API calls are routed through the Infor ION API Gateway, which handles OAuth 2.0 authentication. There is no way to call the IDM API directly without going through the gateway. [src5, src8]

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Resource Owner Grant (password) | Backend/server-to-server integrations, no user present | ~2h (default, configurable per app) | Yes (30-day grant lifetime default) | Uses service account accessKey/secretKey as username/password. Recommended for integrations. [src8] |
| Authorization Code Grant | Web/mobile apps with user interaction | ~2h (default) | Yes | User authorizes app; requires callback URL. [src5] |
| SAML Bearer Grant | Apps already authenticated within Infor OS/Ming.le | Session-bound | No | Reuses existing SSO token. [src6] |
| Implicit Grant | Single-page apps (legacy) | ~2h | No | Not recommended for new development. [src6] |

[src5, src6, src8]

### Authentication Gotchas

- **Service account credentials are NOT the same as user credentials**: The Resource Owner grant uses `accessKey` as `username` and `secretKey` as `password`, NOT a human user's login credentials. These are generated from the ION API Authorized Apps configuration. [src5, src8]
- **The .ionapi credentials file contains all connection details**: Download it from ION Desk > ION API > Authorized Apps. It includes `ci` (client ID), `cs` (client secret), `saak` (service account access key), `sask` (service account secret key), `iu` (ION API base URL), `pu` (portal URL), and `oa` (OAuth token endpoint). [src6]
- **Idle refresh tokens expire after 30 days**: If your integration is dormant for over 30 days, the refresh token is invalidated and you must re-authenticate from scratch. Set grant lifetime to 0 for indefinite validity if needed. [src8]
- **ION API Gateway adds latency**: Every IDM API call goes through the gateway, adding 50-200ms overhead compared to direct service calls. Factor this into performance calculations. [src5]

## Constraints

- **No field-level projection on search results** — search responses return full document metadata and base64-encoded file content by default; you cannot request only specific fields. This makes naive search queries extremely expensive for large result sets. [src7]
- **Only major versioning (1.0, 2.0, etc.)** — IDM does not support minor version increments; every check-in creates a new major version. You cannot create version 1.1 or 1.2. [src2]
- **Document types and attributes must be pre-configured** — you cannot create document types or custom attributes via the API. An IDM administrator must configure them through the IDM Administration UI (OS > Document Management > Administration > Document Type). [src4]
- **All API calls must traverse the ION API Gateway** — no direct IDM endpoint access is possible in cloud deployments. This means ION API Gateway availability is a dependency for all document operations. [src5]
- **Check-out is exclusive** — when a document is checked out, no other user or integration can modify it until it is checked in or the checkout is discarded. Searched results exclude checked-out documents. [src2]
- **Base64 encoding inflates payload by ~33%** — a 1.5 GB file becomes ~2 GB when base64-encoded, leaving almost no headroom under the 2 GB upload limit. [src1, src4]
- **Subscription tier determines daily quotas** — Essentials tier (15K included docs) has 75K retrievals/day and 600 peak processing/min, which can be a bottleneck for integration-heavy workloads. [src1]

## Integration Pattern Decision Tree

```
START — User needs to integrate with Infor IDM
|-- What's the operation?
|   |-- Upload single document
|   |   |-- File size < 20 MB?
|   |   |   |-- YES --> POST /IDM/api/items (synchronous, JSON/XML with base64) [src4]
|   |   |   |-- NO, but < 2 GB --> POST /IDM/api/items (background processing, async) [src1]
|   |   |   |-- NO, > 2 GB --> Split into parts or use IDM Utilities bulk import [src1]
|   |-- Search for documents
|   |   |-- Need metadata only?
|   |   |   |-- YES --> GET /IDM/api/items/search with XQuery, then fetch content separately [src7]
|   |   |   |-- NO, need file content too --> Search first, then GET /items/{pid}/resource/stream per doc [src7]
|   |   |-- Large result set (>100 docs)?
|   |       |-- YES --> Use $offset and $limit pagination, process in batches [src7]
|   |       |-- NO --> Single search request is fine
|   |-- Download document file
|   |   --> GET /IDM/api/items/{pid}/resource/stream [src7]
|   |-- Bulk import (hundreds/thousands of files)
|   |   --> Use IDM Utilities File Import tool (not REST API) [src3]
|   |-- Event-driven document routing
|       --> Use ION Document Flows with BOD-triggered API calls [src4]
|-- Authentication
|   |-- Backend integration (no user present)
|   |   --> Resource Owner grant with service account [src8]
|   |-- Web/mobile app (user present)
|   |   --> Authorization Code grant [src5]
|   |-- Already inside Infor OS
|       --> SAML Bearer grant (reuse SSO token) [src6]
|-- Error tolerance?
    |-- Zero-loss required --> Implement idempotency via attribute-based duplicate checks before upload
    |-- Best-effort --> Fire-and-forget with retry on 4xx/5xx
```

## Quick Reference

| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Upload document | POST | /IDM/api/items | JSON/XML with base64 file content | Requires entityName, attrs, resrs, acl [src4] |
| Search documents | GET | /IDM/api/items/search | XQuery in query parameter | Use $offset and $limit for pagination [src7] |
| Get document metadata | GET | /IDM/api/items/{pid} | N/A | Returns attributes and version info [src2] |
| Download file content | GET | /IDM/api/items/{pid}/resource/stream | N/A | Returns binary stream [src7] |
| Check out document | POST | /IDM/api/items/{pid}/checkout | N/A | Locks document for exclusive editing [src2] |
| Check in document | POST | /IDM/api/items/{pid}/checkin | Updated file content | Creates new major version [src2] |
| Discard checkout | POST | /IDM/api/items/{pid}/discardCheckout | N/A | Releases lock without saving changes [src2] |
| Delete document | DELETE | /IDM/api/items/{pid} | N/A | Permanent removal; requires appropriate ACL permissions [src2] |
| Update metadata | PUT | /IDM/api/items/{pid} | JSON/XML with updated attrs | Updates attributes without new version [src2] |

## Step-by-Step Integration Guide

### 1. Obtain ION API credentials

Navigate to Infor ION Desk > ION API > Authorized Apps. Create a new Backend Service type authorized app for your integration. Download the .ionapi credentials file which contains client_id, client_secret, service account access key, and secret key, along with the OAuth token endpoint URL. [src5, src6]

```bash
# The .ionapi file is a JSON file with this structure:
# {
#   "ci": "CLIENT_ID",
#   "cs": "CLIENT_SECRET",
#   "saak": "SERVICE_ACCOUNT_ACCESS_KEY",
#   "sask": "SERVICE_ACCOUNT_SECRET_KEY",
#   "iu": "https://{tenant}.ionapi.infor.com",
#   "pu": "https://{tenant}.portal.infor.com",
#   "oa": "https://{tenant}.portal.infor.com/IONSERVICES/oauth/token"
# }
```

**Verify**: The .ionapi file downloads successfully and contains all required fields. Test the token endpoint URL is reachable.

### 2. Acquire OAuth 2.0 access token

Use the Resource Owner grant to exchange service account credentials for an access token. The token endpoint is specified in the `oa` field of the .ionapi file. [src8]

```bash
# Input:  .ionapi credentials (ci, cs, saak, sask, oa)
# Output: OAuth 2.0 access token (Bearer)

TOKEN=$(curl -s -X POST "${TOKEN_ENDPOINT}" \
  -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: ${TOKEN:0:20}..."
```

**Verify**: `echo $TOKEN` returns a non-empty JWT string. Token is valid for ~2 hours by default.

### 3. Upload a document to IDM

Post a document with metadata attributes, file content (base64-encoded), document type (entityName), and access control list. All attributes defined as required on the Document Type must be included. [src4]

```bash
# Input:  access token, document file, document type name, attribute values
# Output: Created item ID (pid)

# Base64 encode the file
FILE_B64=$(base64 -w 0 invoice.pdf)

curl -s -X POST "${ION_API_BASE}/IDM/api/items" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"item\": {
      \"attrs\": {
        \"attr\": [
          {\"name\": \"Company_Number\", \"value\": \"1000\"},
          {\"name\": \"Invoice_Number\", \"value\": \"INV-2026-0042\"}
        ]
      },
      \"resrs\": {
        \"res\": [
          {\"filename\": \"invoice.pdf\", \"base64\": \"${FILE_B64}\"}
        ]
      },
      \"acl\": {\"name\": \"Public\"},
      \"entityName\": \"Invoice\"
    }
  }" | jq .
```

**Verify**: Response contains a `pid` (persistent item ID). Check the document appears in IDM UI under the specified document type.

### 4. Search for documents by attributes

Use XQuery syntax on the search endpoint. Build queries using the IDM Query Builder UI first, then toggle "Enter Query Manually" to see the generated XQuery syntax for use in API calls. [src7]

```bash
# Input:  access token, XQuery filter
# Output: List of matching document metadata

curl -s -G "${ION_API_BASE}/IDM/api/items/search" \
  --data-urlencode "q=/Invoice[@Company_Number = '1000' AND @Invoice_Number = 'INV-2026-0042']" \
  --data-urlencode "\$offset=0" \
  --data-urlencode "\$limit=10" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Accept: application/json" | jq .
```

**Verify**: Response contains matching items with their `pid` values and attribute metadata.

### 5. Download document file content

Retrieve the binary file content using the resource stream endpoint. This is the recommended approach instead of relying on base64-encoded content from search results. [src7]

```bash
# Input:  access token, document pid
# Output: Binary file content saved to disk

curl -s "${ION_API_BASE}/IDM/api/items/${DOCUMENT_PID}/resource/stream" \
  -H "Authorization: Bearer ${TOKEN}" \
  -o downloaded_document.pdf

ls -la downloaded_document.pdf
```

**Verify**: Downloaded file matches the original file size and opens correctly.

### 6. Implement error handling and token refresh

Handle common error scenarios: expired tokens (401), rate limits, and timeout on large uploads. Implement proactive token refresh before the ~2h expiry window. [src5, src8]

```bash
# Check if token is still valid before each operation
# If 401, re-acquire token using the same Resource Owner grant

# For rate limit handling, check response headers for retry guidance
# For upload timeout (30 min limit), consider chunking very large files
```

**Verify**: Integration handles 401 responses by re-authenticating without manual intervention.

## Code Examples

### Python: Upload a Document to IDM

```python
# Input:  .ionapi credentials file path, document file path, document type, attributes
# Output: Created document PID in IDM

import json
import base64
import requests  # requests==2.31.0

def get_idm_token(ionapi_path):
    """Acquire OAuth 2.0 token using Resource Owner grant."""
    with open(ionapi_path) as f:
        creds = json.load(f)

    resp = requests.post(
        creds["oa"],
        data={
            "grant_type": "password",
            "username": creds["saak"],
            "password": creds["sask"],
            "client_id": creds["ci"],
            "client_secret": creds["cs"],
        },
    )
    resp.raise_for_status()
    return creds["iu"], resp.json()["access_token"]


def upload_document(ionapi_path, file_path, entity_name, attributes, acl="Public"):
    """Upload a single document to Infor IDM via REST API."""
    base_url, token = get_idm_token(ionapi_path)

    # Base64-encode the file content
    with open(file_path, "rb") as f:
        file_b64 = base64.b64encode(f.read()).decode("utf-8")

    filename = file_path.rsplit("/", 1)[-1] if "/" in file_path else file_path

    payload = {
        "item": {
            "attrs": {
                "attr": [{"name": k, "value": v} for k, v in attributes.items()]
            },
            "resrs": {
                "res": [{"filename": filename, "base64": file_b64}]
            },
            "acl": {"name": acl},
            "entityName": entity_name,
        }
    }

    resp = requests.post(
        f"{base_url}/IDM/api/items",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=1800,  # 30-minute timeout to match IDM server limit
    )
    resp.raise_for_status()
    return resp.json()


# Example usage
result = upload_document(
    ionapi_path="credentials.ionapi",
    file_path="invoice.pdf",
    entity_name="Invoice",
    attributes={"Company_Number": "1000", "Invoice_Number": "INV-2026-0042"},
)
print(f"Document created: PID={result.get('pid')}")
```

### JavaScript/Node.js: Search and Download Documents

```javascript
// Input:  .ionapi credentials, XQuery search string
// Output: Downloaded file saved to disk

// npm install axios@1.6.0
const axios = require("axios");
const fs = require("fs");

async function getToken(creds) {
  const resp = await axios.post(
    creds.oa,
    new URLSearchParams({
      grant_type: "password",
      username: creds.saak,
      password: creds.sask,
      client_id: creds.ci,
      client_secret: creds.cs,
    }),
    { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
  );
  return resp.data.access_token;
}

async function searchAndDownload(ionapiPath, xquery, outputDir) {
  const creds = JSON.parse(fs.readFileSync(ionapiPath, "utf-8"));
  const token = await getToken(creds);
  const baseUrl = creds.iu;

  // Step 1: Search for documents (metadata only)
  const searchResp = await axios.get(`${baseUrl}/IDM/api/items/search`, {
    params: { q: xquery, $offset: 0, $limit: 50 },
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
  });

  const items = searchResp.data.items || [];
  console.log(`Found ${items.length} documents`);

  // Step 2: Download each file separately via resource/stream
  for (const item of items) {
    const pid = item.pid;
    const fileResp = await axios.get(
      `${baseUrl}/IDM/api/items/${pid}/resource/stream`,
      {
        headers: { Authorization: `Bearer ${token}` },
        responseType: "arraybuffer",
      }
    );
    const filename = item.filename || `${pid}.bin`;
    fs.writeFileSync(`${outputDir}/${filename}`, fileResp.data);
    console.log(`Downloaded: ${filename}`);
  }
}

// Example usage
searchAndDownload(
  "credentials.ionapi",
  "/Invoice[@Company_Number = '1000']",
  "./downloads"
).catch(console.error);
```

### cURL: Quick API Test

```bash
# Input:  .ionapi credentials, ION API base URL
# Output: Token + document search results

# Step 1: Parse .ionapi file and acquire token
CI=$(jq -r '.ci' credentials.ionapi)
CS=$(jq -r '.cs' credentials.ionapi)
SAAK=$(jq -r '.saak' credentials.ionapi)
SASK=$(jq -r '.sask' credentials.ionapi)
OA=$(jq -r '.oa' credentials.ionapi)
IU=$(jq -r '.iu' credentials.ionapi)

TOKEN=$(curl -s -X POST "$OA" \
  -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')

# Step 2: Search for documents
curl -s -G "$IU/IDM/api/items/search" \
  --data-urlencode "q=/Invoice[@Company_Number = '1000']" \
  --data-urlencode "\$offset=0" \
  --data-urlencode "\$limit=5" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" | jq '.items | length'

# Expected output: number of matching documents (e.g., 3)
```

## Data Mapping

### IDM Item Payload Structure

| Field Path | Type | Required | Description | Gotcha |
|---|---|---|---|---|
| item.entityName | String | Yes | Document Type name (must match IDM admin config exactly) | Case-sensitive; must be pre-configured in IDM Administration [src4] |
| item.attrs.attr[].name | String | Yes | Attribute name as defined on the Document Type | Must match configured attribute names exactly [src4] |
| item.attrs.attr[].value | String | Yes | Attribute value | All values are strings, even numbers and dates [src4] |
| item.resrs.res[].filename | String | Yes | File name including extension | Extension determines MIME type handling in IDM [src4] |
| item.resrs.res[].base64 | String | Yes | Base64-encoded file content | Inflates payload by ~33%; 2 GB file limit applies to decoded size [src1] |
| item.acl.name | String | Yes | Access Control List name | Must reference a pre-configured ACL in IDM [src4] |

### XQuery Search Syntax

| Pattern | XQuery Syntax | Notes |
|---|---|---|
| Exact match | `/@AttrName = 'value'` | Case-sensitive string comparison |
| AND filter | `/@Attr1 = 'val1' AND @Attr2 = 'val2'` | Both conditions required |
| OR filter | `/@Attr1 = 'val1' OR @Attr1 = 'val2'` | Either condition |
| Document type filter | `/DocumentTypeName[@Attr = 'val']` | Root element is the entityName |
| Variable substitution | `${FN}${variableName}${FN}` | Used in ION Document Flow mappings only [src7] |

### Data Type Gotchas

- **All attribute values are strings**: Even for date and numeric attributes, the API accepts and returns string values. If you need to filter by date range, the XQuery comparison is lexicographic unless the attribute is specifically typed in IDM. [src4]
- **Filename determines MIME type**: IDM infers document content type from the filename extension. Uploading a PDF as "document.txt" will cause viewing issues in the IDM UI. [src4]
- **Base64 encoding inflates file size by ~33%**: A 1.5 GB file becomes approximately 2 GB after base64 encoding, effectively reducing the usable upload limit to ~1.5 GB for API uploads. For files near the limit, use IDM Utilities instead. [src1]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Invalid or expired token | OAuth token expired (default ~2h) or malformed | Re-acquire token via Resource Owner grant. Check .ionapi credentials are correct. [src5, src8] |
| 403 Forbidden | Insufficient permissions | User/service account lacks IDM privileges (IONDeskAdmin, IONAPI-Administrator) | Grant required ION Desk and API Gateway roles to the service account user. [src3] |
| 400 Bad Request | Malformed payload | Missing required attributes, invalid entityName, or malformed base64 | Verify all required Document Type attributes are included; check entityName matches exactly. [src4] |
| 404 Not Found | Document not found | Invalid PID or document was deleted | Verify PID exists; search for document by attributes first. [src2] |
| 409 Conflict | Document locked | Document is checked out by another user/process | Wait for check-in or discard checkout; implement retry with backoff. [src2] |
| 413 Payload Too Large | File exceeds size limit | Decoded file content exceeds 2 GB | Split file or use IDM Utilities for bulk import. [src1] |
| 408 Request Timeout | Upload took too long | Upload exceeded 30-minute timeout | Reduce file size; check network bandwidth; use background upload for files > 20 MB. [src1] |
| 429 Too Many Requests | Rate limit exceeded | Exceeded peak rate (600-2,500/min depending on tier and operation) | Implement exponential backoff; check subscription tier limits. [src1] |

[src1, src2, src4, src5]

### Failure Points in Production

- **Search returning enormous payloads**: By default, search results include base64-encoded file content for every matching document. A search returning 500 documents with 5 MB files each attempts to build a 3+ GB response. Fix: `Always use $limit to cap results. Fetch file content separately via /items/{pid}/resource/stream. Never rely on file content from search responses.` [src7]
- **Token expiry mid-batch**: A batch processing hundreds of documents over >2 hours uses an expired token partway through. Fix: `Check token expiry before each batch of requests. Re-acquire the token proactively when approaching the 2-hour window. Store token acquisition time and refresh at 90% of lifetime.` [src8]
- **Missing required attributes on upload**: The error message "The document could not be created. Make sure that all attributes are correct" is generic and does not identify which attribute is missing. Fix: `Check the Document Type configuration in IDM Administration > Document Type to see all required attributes before building the payload. Log the full payload on failure for debugging.` [src4]
- **Checked-out document blocking integration**: If a user checks out a document via the UI and forgets to check it back in, API updates to that document fail with 409. Fix: `Implement a monitoring process that checks for stale checkouts. Use discard checkout API as an admin override when appropriate.` [src2]
- **Idle refresh token expiration after 30 days**: Integrations that run infrequently (e.g., monthly batch jobs) may find their refresh token invalidated. Fix: `Set the Authorized App's grant lifetime to 0 for indefinite validity, or implement a keep-alive mechanism that refreshes the token at least once every 30 days.` [src8]
- **ION API Gateway outage blocks all document operations**: Since all IDM API calls must traverse the gateway, a gateway outage makes IDM completely inaccessible via API. Fix: `Implement a dead letter queue for failed document operations and replay them when the gateway recovers. Monitor ION API Gateway health separately from IDM health.` [src5]

## Anti-Patterns

### Wrong: Fetching file content through search results

```python
# BAD — search returns base64 content for every matching document,
# creating enormous payloads and potentially timing out
results = requests.get(f"{base_url}/IDM/api/items/search",
    params={"q": "/Invoice[@Company_Number = '1000']"},
    headers=headers)
for item in results.json()["items"]:
    file_content = base64.b64decode(item["base64"])  # Huge payload already loaded
```

### Correct: Search for metadata, then download files individually

```python
# GOOD — search returns only metadata (with pagination),
# then fetch each file via the stream endpoint
results = requests.get(f"{base_url}/IDM/api/items/search",
    params={"q": "/Invoice[@Company_Number = '1000']", "$offset": 0, "$limit": 50},
    headers=headers)
for item in results.json()["items"]:
    pid = item["pid"]
    file_resp = requests.get(
        f"{base_url}/IDM/api/items/{pid}/resource/stream",
        headers=headers)
    with open(f"downloads/{item['filename']}", "wb") as f:
        f.write(file_resp.content)
```

### Wrong: Uploading large files without timeout handling

```python
# BAD — no timeout, no retry, no progress tracking for large files
requests.post(f"{base_url}/IDM/api/items",
    json=payload_with_500mb_base64,
    headers=headers)
```

### Correct: Handle timeouts and use background upload awareness

```python
# GOOD — explicit timeout matching IDM's 30-min limit, with retry
import time

def upload_with_retry(url, payload, headers, max_retries=3):
    for attempt in range(max_retries):
        try:
            resp = requests.post(url, json=payload, headers=headers,
                                 timeout=1800)  # 30 min
            resp.raise_for_status()
            return resp.json()
        except requests.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt * 10)
                # Re-acquire token in case it expired during the long upload
                headers["Authorization"] = f"Bearer {get_fresh_token()}"
            else:
                raise
```

### Wrong: Hardcoding document type attributes

```python
# BAD — attributes change when IDM admin modifies the Document Type
payload = {
    "item": {
        "attrs": {"attr": [
            {"name": "Company", "value": "1000"},  # Name might be "Company_Number"
            {"name": "Date", "value": "2026-03-01"},
        ]},
        "entityName": "invoice",  # Case might be wrong: "Invoice"
    }
}
```

### Correct: Read attribute names from IDM configuration, match case exactly

```python
# GOOD — attribute names and entityName are case-sensitive and must match
# IDM Administration > Document Type configuration exactly
# Validate against known schema before sending
DOCUMENT_TYPES = {
    "Invoice": {
        "required": ["Company_Number", "Invoice_Number"],
        "optional": ["Invoice_Date", "Vendor_Name"],
    }
}

def build_payload(entity_name, attrs, filename, file_b64, acl="Public"):
    schema = DOCUMENT_TYPES.get(entity_name)
    if not schema:
        raise ValueError(f"Unknown document type: {entity_name}")
    missing = [a for a in schema["required"] if a not in attrs]
    if missing:
        raise ValueError(f"Missing required attributes: {missing}")
    return {
        "item": {
            "attrs": {"attr": [{"name": k, "value": v} for k, v in attrs.items()]},
            "resrs": {"res": [{"filename": filename, "base64": file_b64}]},
            "acl": {"name": acl},
            "entityName": entity_name,
        }
    }
```

## Common Pitfalls

- **Confusing Infor IDM with ForgeRock IDM (PingIDM)**: These are completely different products. Infor IDM is an enterprise content management system; ForgeRock IDM is an identity management platform. Search results frequently intermix the two. Fix: `Always specify "Infor Document Management" or "Infor OS IDM" in searches and documentation.` [src2]
- **Not accounting for base64 inflation in file size planning**: A 1.5 GB file becomes ~2 GB after base64 encoding, hitting the upload limit. Fix: `Calculate the base64-encoded size (original * 1.37) before attempting upload. For files near the limit, use IDM Utilities batch import instead.` [src1]
- **Assuming API-created documents appear instantly**: Documents uploaded via API may be processed asynchronously (especially files > 20 MB background threshold). Fix: `Implement a polling mechanism to verify document availability after upload. Don't immediately search for a just-uploaded document.` [src1]
- **Using the Copy Document feature for large files**: The Copy Document operation has a separate 50 MB limit, much lower than the 2 GB upload limit. Fix: `Use the standard upload API for files > 50 MB instead of the copy endpoint.` [src1]
- **Not paginating search results**: A search without $limit can return thousands of results with base64 content, causing client-side memory exhaustion. Fix: `Always include $offset and $limit parameters. Process results in batches of 50-100 maximum.` [src7]
- **Relying on search for real-time change detection**: IDM search indexes are not updated in real-time; there can be a short delay between upload and searchability. Fix: `If you need confirmation of a specific upload, store the returned PID and query by PID directly rather than searching by attributes.` [src2]

## Diagnostic Commands

```bash
# Test ION API Gateway authentication
CI=$(jq -r '.ci' credentials.ionapi)
CS=$(jq -r '.cs' credentials.ionapi)
SAAK=$(jq -r '.saak' credentials.ionapi)
SASK=$(jq -r '.sask' credentials.ionapi)
OA=$(jq -r '.oa' credentials.ionapi)
IU=$(jq -r '.iu' credentials.ionapi)

TOKEN=$(curl -s -X POST "$OA" \
  -d "grant_type=password&username=$SAAK&password=$SASK&client_id=$CI&client_secret=$CS" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  | jq -r '.access_token')

echo "Token length: ${#TOKEN} (should be >100)"

# Test IDM API connectivity
curl -s -o /dev/null -w "%{http_code}" "$IU/IDM/api/items/search" \
  -H "Authorization: Bearer $TOKEN" \
  -G --data-urlencode "q=/*" --data-urlencode "\$limit=1"
# Expected: 200

# Check document count for a specific type
curl -s -G "$IU/IDM/api/items/search" \
  --data-urlencode "q=/Invoice" \
  --data-urlencode "\$limit=0" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" | jq '.totalCount'

# Verify a specific document exists
curl -s "$IU/IDM/api/items/${DOCUMENT_PID}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" | jq '{pid: .pid, entityName: .entityName, version: .version}'

# Test file download
curl -s -o /dev/null -w "Download: %{size_download} bytes, Time: %{time_total}s, HTTP: %{http_code}\n" \
  "$IU/IDM/api/items/${DOCUMENT_PID}/resource/stream" \
  -H "Authorization: Bearer $TOKEN"
```

## Version History & Compatibility

| Infor OS Release | Date | IDM API Changes | Migration Notes |
|---|---|---|---|
| 2025.x | 2025-Q1 | Large file API threshold made configurable | Previously hardcoded at 2 MB; now configurable per deployment [src1] |
| 2024.x | 2024-Q1 | Background upload threshold configurable | Default 20 MB; can be adjusted by admin [src1] |
| 2023.x | 2023-Q1 | Updated Swagger/OpenAPI documentation | API endpoints unchanged; improved documentation [src2] |
| 2022.x | 2022-Q1 | ION Document Flow support for IDM upload | Enabled event-driven document routing via BOD triggers [src4] |
| 2021.x | 2021-Q1 | XQuery search API improvements | Added $offset and $limit pagination parameters [src7] |

[src1, src2]

### Deprecation Policy

Infor follows a rolling release model for Infor OS. IDM API changes are typically backward-compatible within major releases. Breaking changes are announced through Infor OS release notes, typically with one major release cycle (~6-12 months) of advance notice. The IDM REST API does not use formal version numbering (v1, v2) — all changes are tied to the Infor OS release version. Always test integrations against new Infor OS releases before upgrading production. [src2]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Managing documents within the Infor CloudSuite ecosystem (M3, LN, Syteline) | Need standalone ECM with no Infor dependency | SharePoint, Box, or standalone ECM APIs |
| Integrating document upload/download with Infor ERP workflows | Need to store >2 GB individual files | External blob storage (S3, Azure Blob) with IDM metadata reference |
| Automated document classification and attribute-based search | Need full-text content search across document bodies | Elasticsearch or dedicated search platform alongside IDM |
| Compliance and versioning with check-in/check-out workflow | Need high-throughput document ingestion (>1.5M docs/day) | Upgrade to Enterprise tier or use external staging with batch import |
| ION-integrated event-driven document routing | Need to programmatically create new Document Types | IDM Administration UI (no API for type creation) |

## Important Caveats

- Subscription tier dramatically affects daily quotas: Essentials tier allows only 75,000 operations/day with 600 peak processing/min, while Enterprise allows 1.5M/day with 2,000/min. Size your integration accordingly. [src1]
- The IDM REST API does not have formal versioning — endpoints can change between Infor OS releases without a version increment. Always test integrations when upgrading Infor OS. [src2]
- On-premises Infor OS deployments share the same API surface but may have different infrastructure constraints (file system storage limits, network configuration). This card covers cloud deployments specifically. [src2]
- ION API Gateway availability is a hard dependency for all IDM API operations. Gateway maintenance windows affect document management integrations. [src5]
- Document type configuration (entityName, attributes, ACLs) must be done by an IDM administrator through the UI — there is no API for metadata schema management. Changes to document types may require integration code updates. [src4]

## Related Units

- [Infor ION API Gateway OAuth 2.0 authentication](/business/erp-integration/infor-ion-api-authentication/2026)
- [Infor M3 API capabilities](/business/erp-integration/infor-m3-api/2026)
- [Cross-ERP document management integration playbook](/business/erp-integration/erp-document-management-integration/2026)
