Infor IDM Document Management API: Capabilities, Endpoints, and Constraints

Type: ERP Integration System: Infor Document Management (IDM) (Infor OS 2024.x/2025.x) Confidence: 0.78 Sources: 8 Verified: 2026-03-02 Freshness: 2026-03-02

TL;DR

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]

PropertyValue
VendorInfor
SystemInfor Document Management (IDM), Infor OS 2024.x/2025.x
API SurfaceREST (JSON/XML) via ION API Gateway
Current API VersionIDM API v1 (no formal versioning; tied to Infor OS release)
Editions CoveredEssentials (15K docs), Professional (75K docs), Enterprise (300K docs)
DeploymentCloud (Infor CloudSuite)
API DocsInfor Developer Portal — Document Management
StatusGA

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 SurfaceProtocolBest ForMax PayloadRate LimitReal-time?Bulk?
IDM REST API (Items)HTTPS/JSON or XMLDocument CRUD, metadata management2 GB per file (base64)Tier-dependent: 75K-1.5M ops/dayYesNo
IDM Items SearchHTTPS/JSON or XMLDocument discovery via XQueryResponse can be very largeTier-dependent: 75K-1.5M searches/dayYesNo
IDM Resource StreamHTTPS/binaryFile content download2 GBShared with retrieval quotaYesNo
IDM Utilities (Batch)CLI/GUI toolBulk import, export, batch updateN/A (tool-based)Not API-rate-limitedNoYes
ION Document FlowsION Process DesignerEvent-driven document routingPer-BOD limitsION message limitsEvent-drivenYes

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max file size per upload2 GBPOST /IDM/api/itemsGlobal system limit; base64 encoding increases payload by ~33% [src1]
Max file size for Copy Document50 MBCopy operations onlyMuch lower than standard upload [src1]
Upload timeout30 minutesAll upload operationsConnection drops if exceeded [src1]
Background upload threshold20 MB (default, configurable)Large file uploadsFiles above threshold are processed asynchronously [src1]
Large file API threshold2 MB (default, configurable)Resource streamFiles above this use the optimized large file API [src2]
Search paginationoffset + limit params/IDM/api/items/searchNo server-side cursor; offset-based only [src7]

Rolling / Daily Limits

Limit TypeEssentials (1-74,999)Professional (75K-299,999)Enterprise (300K+)Peak Rate
Document retrievals75,000/day750,000/day1,500,000/day1,000-2,500/min
Document processing (upload/update)75,000/day750,000/day1,500,000/day600-2,000/min
Searches75,000/day750,000/day1,500,000/day1,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]

FlowUse WhenToken LifetimeRefresh?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. Recommended for integrations. [src8]
Authorization Code GrantWeb/mobile apps with user interaction~2h (default)YesUser authorizes app; requires callback URL. [src5]
SAML Bearer GrantApps already authenticated within Infor OS/Ming.leSession-boundNoReuses existing SSO token. [src6]
Implicit GrantSingle-page apps (legacy)~2hNoNot recommended for new development. [src6]

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — User needs to integrate with Infor IDM
|-- What's the operation?
|   |-- Upload single document
|   |   |-- File size < 20 MB? --> POST /IDM/api/items (synchronous)
|   |   |-- File < 2 GB? --> POST /IDM/api/items (background processing)
|   |   |-- File > 2 GB? --> Split or use IDM Utilities bulk import
|   |-- Search for documents
|   |   |-- Metadata only? --> GET /IDM/api/items/search + separate /resource/stream
|   |   |-- Large result set? --> Use $offset and $limit pagination
|   |-- Download document --> GET /IDM/api/items/{pid}/resource/stream
|   |-- Bulk import --> Use IDM Utilities File Import tool
|   |-- Event-driven --> Use ION Document Flows
|-- Authentication
|   |-- Backend integration --> Resource Owner grant with service account
|   |-- Web/mobile app --> Authorization Code grant
|   |-- Inside Infor OS --> SAML Bearer grant
|-- Error tolerance?
    |-- Zero-loss --> Attribute-based duplicate checks before upload
    |-- Best-effort --> Fire-and-forget with retry on 4xx/5xx

Quick Reference

OperationMethodEndpointPayloadNotes
Upload documentPOST/IDM/api/itemsJSON/XML with base64 fileRequires entityName, attrs, resrs, acl [src4]
Search documentsGET/IDM/api/items/searchXQuery in query paramUse $offset/$limit for pagination [src7]
Get document metadataGET/IDM/api/items/{pid}N/AReturns attributes and version info [src2]
Download file contentGET/IDM/api/items/{pid}/resource/streamN/AReturns binary stream [src7]
Check out documentPOST/IDM/api/items/{pid}/checkoutN/ALocks for exclusive editing [src2]
Check in documentPOST/IDM/api/items/{pid}/checkinUpdated file contentCreates new major version [src2]
Discard checkoutPOST/IDM/api/items/{pid}/discardCheckoutN/AReleases lock without changes [src2]
Delete documentDELETE/IDM/api/items/{pid}N/ARequires appropriate ACL [src2]
Update metadataPUT/IDM/api/items/{pid}JSON/XML with updated attrsNo new version created [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. Download the .ionapi credentials file containing client_id, client_secret, service account keys, and OAuth token endpoint URL. [src5, src6]

// The .ionapi file 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 and contains all required fields.

2. Acquire OAuth 2.0 access token

Use the Resource Owner grant to exchange service account credentials for an access token. [src8]

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')

Verify: echo $TOKEN returns a non-empty JWT string. Token valid for ~2 hours.

3. Upload a document to IDM

Post a document with metadata attributes, base64-encoded file content, document type (entityName), and ACL. [src4]

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).

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 generated XQuery. [src7]

curl -s -G "${ION_API_BASE}/IDM/api/items/search" \
  --data-urlencode "q=/Invoice[@Company_Number = '1000']" \
  --data-urlencode "\$offset=0" \
  --data-urlencode "\$limit=10" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Accept: application/json" | jq .

Verify: Response contains matching items with pid values.

5. Download document file content

Retrieve binary file content via the resource stream endpoint. [src7]

curl -s "${ION_API_BASE}/IDM/api/items/${DOCUMENT_PID}/resource/stream" \
  -H "Authorization: Bearer ${TOKEN}" \
  -o downloaded_document.pdf

Verify: Downloaded file matches original size and opens correctly.

6. Implement error handling and token refresh

Handle expired tokens (401), rate limits (429), and upload timeouts. Proactively refresh tokens before the ~2h expiry. [src5, src8]

# Re-acquire token on 401; back off on 429
# For uploads > 30 min timeout, consider chunking or IDM Utilities

Verify: Integration handles 401 by re-authenticating without manual intervention.

Code Examples

Python: Upload a Document to IDM

# Input:  .ionapi credentials file path, document file path, document type, attributes
# Output: Created document PID in IDM

import json, base64
import requests  # requests==2.31.0

def get_idm_token(ionapi_path):
    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"):
    base_url, token = get_idm_token(ionapi_path)
    with open(file_path, "rb") as f:
        file_b64 = base64.b64encode(f.read()).decode("utf-8")
    filename = file_path.rsplit("/", 1)[-1]
    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)
    resp.raise_for_status()
    return resp.json()

result = upload_document("credentials.ionapi", "invoice.pdf", "Invoice",
    {"Company_Number": "1000", "Invoice_Number": "INV-2026-0042"})
print(f"Document created: PID={result.get('pid')}")

JavaScript/Node.js: Search and Download Documents

// Input:  .ionapi credentials, XQuery search string
// Output: Downloaded files saved to disk
// npm install [email protected]
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 searchResp = await axios.get(`${creds.iu}/IDM/api/items/search`, {
    params: { q: xquery, $offset: 0, $limit: 50 },
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
  });
  for (const item of searchResp.data.items || []) {
    const fileResp = await axios.get(
      `${creds.iu}/IDM/api/items/${item.pid}/resource/stream`,
      { headers: { Authorization: `Bearer ${token}` }, responseType: "arraybuffer" });
    fs.writeFileSync(`${outputDir}/${item.filename || item.pid}`, fileResp.data);
  }
}

searchAndDownload("credentials.ionapi", "/Invoice[@Company_Number='1000']", "./downloads")
  .catch(console.error);

cURL: Quick API Test

# Input:  .ionapi credentials file
# Output: Token + document search results

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')

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: number of matching documents

Data Mapping

IDM Item Payload Structure

Field PathTypeRequiredDescriptionGotcha
item.entityNameStringYesDocument Type name (must match IDM admin config)Case-sensitive [src4]
item.attrs.attr[].nameStringYesAttribute name as defined on Document TypeMust match exactly [src4]
item.attrs.attr[].valueStringYesAttribute valueAll values are strings, even numbers/dates [src4]
item.resrs.res[].filenameStringYesFile name with extensionExtension determines MIME type [src4]
item.resrs.res[].base64StringYesBase64-encoded file content~33% size inflation [src1]
item.acl.nameStringYesAccess Control List nameMust be pre-configured in IDM [src4]

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401UnauthorizedExpired or invalid OAuth tokenRe-acquire token via Resource Owner grant [src5]
403ForbiddenInsufficient ION Desk / API Gateway rolesGrant IONDeskAdmin and IONAPI-Administrator roles [src3]
400Bad RequestMissing required attributes or invalid entityNameVerify Document Type config; check case sensitivity [src4]
404Not FoundInvalid PID or deleted documentSearch by attributes first [src2]
409ConflictDocument is checked out by another userWait for check-in or discard checkout [src2]
413Payload Too LargeFile exceeds 2 GBSplit file or use IDM Utilities [src1]
408Request TimeoutUpload exceeded 30-minute timeoutReduce file size; check bandwidth [src1]
429Too Many RequestsExceeded peak rate for subscription tierExponential backoff; check tier limits [src1]

Failure Points in Production

Anti-Patterns

Wrong: Fetching file content through search results

# BAD -- search returns base64 content for every match, creating enormous payloads
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"])

Correct: Search metadata, then download files individually

# GOOD -- search with pagination, fetch files via 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"]:
    file_resp = requests.get(
        f"{base_url}/IDM/api/items/{item['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

# BAD -- no timeout, no retry for large files
requests.post(f"{base_url}/IDM/api/items", json=payload_500mb, headers=headers)

Correct: Handle timeouts and implement retry logic

# 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)
            resp.raise_for_status()
            return resp.json()
        except requests.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt * 10)
                headers["Authorization"] = f"Bearer {get_fresh_token()}"
            else:
                raise

Wrong: Hardcoding document type attributes

# BAD -- attribute names and entityName are case-sensitive
payload = {"item": {"attrs": {"attr": [
    {"name": "Company", "value": "1000"}]},
    "entityName": "invoice"}}

Correct: Validate against IDM configuration schema

# GOOD -- validate attribute names and entityName against known schema
DOCUMENT_TYPES = {"Invoice": {"required": ["Company_Number", "Invoice_Number"]}}
def build_payload(entity_name, attrs, filename, file_b64):
    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": "Public"}, "entityName": entity_name}}

Common Pitfalls

Diagnostic Commands

# 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" \
  -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"

# 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 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}'

Version History & Compatibility

Infor OS ReleaseDateIDM API ChangesMigration Notes
2025.x2025-Q1Large file API threshold configurablePreviously hardcoded at 2 MB [src1]
2024.x2024-Q1Background upload threshold configurableDefault 20 MB; admin-adjustable [src1]
2023.x2023-Q1Updated Swagger/OpenAPI docsAPI endpoints unchanged [src2]
2022.x2022-Q1ION Document Flow supportEvent-driven document routing [src4]
2021.x2021-Q1XQuery search improvementsAdded $offset/$limit pagination [src7]

Infor follows a rolling release model for Infor OS. Breaking changes are announced through release notes with one major release cycle (~6-12 months) of advance notice. The IDM REST API does not use formal version numbering. [src2]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Managing documents within Infor CloudSuite (M3, LN, Syteline)Need standalone ECM with no Infor dependencySharePoint, Box, or standalone ECM APIs
Integrating document upload/download with Infor ERP workflowsNeed to store >2 GB individual filesExternal blob storage (S3, Azure Blob) with IDM metadata reference
Compliance and versioning with check-in/check-out workflowNeed high-throughput ingestion (>1.5M docs/day)Upgrade to Enterprise tier or use external staging
ION-integrated event-driven document routingNeed to create Document Types programmaticallyIDM Administration UI (no API for type creation)
Attribute-based document search via XQueryNeed full-text content search across document bodiesElasticsearch or dedicated search platform

Important Caveats

Related Units