Developer ToolsFree generatorPDF, Excel, Word exportInfor M3All Infor M3 tools

Infor M3 CSV to MI API Bulk Loader Script Generator (Python)

This generator writes a Python loader that reads a CSV, maps columns to MI input fields and calls one M3 transaction per row through m3api-rest, logging success or the M3 message for every row. It suits loads of a few hundred to tens of thousands of records where a standard M3 import or MEC flow would be overkill.

Fill in

Generated code
Download:

m3_bulk_load.py

import csv
import os
import time
import requests

BASE_URL = 'https://m3be.acme.local:21108/m3api-rest/execute'
AUTH_MODE = 'basic'
USER = 'M3LOADER'
PROGRAM = 'CRS610MI'
TRANSACTION = 'Add'
CSV_PATH = 'customers.csv'
CONO = '100'
DELAY = 100 / 1000.0
DRY_RUN = 'yes' == 'yes'
MAPPING_TEXT = """customer_no=CUNO
name=CUNM
language=LNCD
currency=CUCD"""

mapping = {}
for line in MAPPING_TEXT.splitlines():
    if '=' in line:
        col, field = line.split('=', 1)
        mapping[col.strip()] = field.strip()

s = requests.Session()
s.headers['Accept'] = 'application/json'
if AUTH_MODE == 'basic':
    s.auth = (USER, os.environ['M3_PASSWORD'])
else:
    s.headers['Authorization'] = 'Bearer ' + os.environ['M3_TOKEN']

url = BASE_URL.rstrip('/') + '/' + PROGRAM + '/' + TRANSACTION + ';cono=' + CONO


def call(params):
    r = s.get(url, params=params, timeout=60)
    try:
        body = r.json()
    except ValueError:
        body = {}
    if r.status_code != 200:
        return False, 'HTTP ' + str(r.status_code) + ': ' + r.text[:300]
    # classic m3api-rest error shape
    if body.get('@type') == 'ServerReturnedNOK' or 'Message' in body:
        return False, str(body.get('Message', body))
    # v2 error shape (if BASE_URL points at a v2 endpoint)
    if body.get('nrOfFailedTransactions'):
        res = (body.get('results') or [{}])[0]
        return False, str(res.get('errorMessage', res))
    return True, 'OK'


ok = failed = 0
with open(CSV_PATH, newline='', encoding='utf-8-sig') as src, open('load_result.csv', 'w', newline='', encoding='utf-8') as out:
    reader = csv.DictReader(src)
    writer = csv.writer(out)
    writer.writerow(['row', 'status', 'message'] + list(mapping.values()))
    for n, row in enumerate(reader, start=2):
        params = {field: (row.get(col) or '').strip() for col, field in mapping.items()}
        if DRY_RUN:
            writer.writerow([n, 'DRY RUN', url] + list(params.values()))
            continue
        success, msg = call(params)
        if success:
            ok += 1
        else:
            failed += 1
        writer.writerow([n, 'OK' if success else 'ERROR', msg] + list(params.values()))
        time.sleep(DELAY)

print('dry run' if DRY_RUN else 'done', '- ok:', ok, 'failed:', failed, '- see load_result.csv')

Run it with dry run first, then against a non-production company. Verify in the M3 API repository (MRS001 and its transaction fields) or the API browser on your server: the transaction name, its mandatory input fields and field lengths. Verify the error shape your 13.x m3api-rest returns for a rejected Add (the script checks the classic Message key and the v2 counters). Numbers and dates must be in the format the API expects, dates usually YYYYMMDD.

Get your load plan reviewed

Send the transaction, field mapping and row count and we will check mandatory fields, formats and the run window before you load.

Optional. The tool and every export work without this. No spam.

Was this tool useful?

How to use it

Set the base URL of m3api-rest on your M3 13.x server, the authentication mode, the MI program and transaction (CRS610MI Add for customers, MMS200MI transactions for items, PPS200MI AddLine for purchase order lines), and the CSV path. Map each CSV column to its MI field with one csvcol=MIFIELD line. Leave dry run on for the first pass: it writes load_result.csv with the URL and parameters per row without calling M3. Set M3_PASSWORD or M3_TOKEN as an environment variable rather than in the file.

  • Row numbers in the result file match spreadsheet rows (header is row 1)
  • Fix failed rows in the source CSV and rerun only those
  • Keep the delay above zero on shared servers

How errors are detected

M3 can answer HTTP 200 for a rejected business transaction, so the status code alone is not enough. The classic m3api-rest endpoint returns an error object with a Message text (and a NOK type) instead of records; a v2 endpoint reports nrOfFailedTransactions and puts an error message in the result entry. The call function checks the status code first, then both shapes, and writes the message as returned, for example a duplicate customer number or a missing mandatory field.

Worked example and duration

Loading 2,000 customers with CRS610MI Add at around 100 ms per call plus a 100 ms delay takes roughly 2,000 x 0.2 s, about 7 minutes. Real call times vary with transaction complexity, validation and server load; the M3 MI API batch load time estimator lets you model that before you schedule. Run loads outside nightly batch peaks so you do not compete with MRP or invoicing jobs, and check a sample of created records on the M3 panel afterwards.

Common mistakes

Values longer than the API field length are rejected or truncated: check lengths in the API repository before loading. Leading zeros disappear when the CSV was saved from a spreadsheet. Decimal commas from European locales break numeric fields. Loading into the production company by mistake is the costly one, which is why CONO is explicit in the URL. Add transactions cannot be undone by rerunning the script, so a failed partial load needs a clean-up plan.

Frequently Asked Questions

Can I use MI APIs for third party integration with M3?

Yes. MI programs are the published interface for reading and writing M3 business objects, and external systems call them through m3api-rest on the M3 server or through ION API. For event-driven flows between systems, ION with BODs or MEC mappings may fit better than direct calls.

How fast can I load data through M3 MI APIs?

One call per row, so duration is rows times call time plus any delay. Simple Add calls often take tens to a few hundred milliseconds each. Measure 50 rows in your environment and extrapolate, then schedule the load outside batch peaks.

Why does M3 return success with an error message?

m3api-rest separates transport from business result. A call can reach M3 and return HTTP 200 while the transaction is rejected, for example for a duplicate key or a missing field. Always inspect the response body for the error message, as this script does.

Should I use a dry run before an M3 data load?

Yes. A dry run shows exactly which parameters each row would send, so you catch wrong mappings, empty mandatory columns and formatting problems before anything is created. Follow it with a small real run in a test company.

If you want loads like this reviewed or run with approval steps, or an assistant that answers questions about M3 data on your own servers, we can help.

ArchRay builder
Design the on-prem AI stack for Infor M3: model, GPUs, RAG pipeline, guardrails
See memory, speed and cost as you swap parts, then export the diagram. Free, in your browser.
Open the builder

Keep exploring

Free tools

ERP fixes and how-tos

Runs with your ERP

Guides

AI on your ERP

Integrations

Comparisons

Software