Description
n8n-nodes-nx3 — IntellX for Sage X3



🇬🇧 English · 🇫🇷 Français
An n8n community node to read, list, create, modify and
describe Sage X3 objects, and to run SQL SELECT queries on the X3
database — from a workflow or from an AI Agent.
The node talks to Sage X3’s CAdxWebServiceXmlCC SOAP web service through the
XCHATX3OBJ sub-program delivered by the ChatX3 patch. You work entirely
with JSON: the node builds the SOAP request, asks X3 to answer in native JSON,
and returns a clean, ready-to-use object. No XML wrangling required.
Contents
1. How it works
2. Requirements
3. Sage X3 setup
4. Installing the node in n8n
5. Credentials
6. Operations
7. Object operations
8. SQL operations
9. Output
10. Advanced options
11. AI Agent and MCP
12. Workflow recipes
13. Troubleshooting
14. Compatibility
15. Support and resources
16. Version history
—
1. How it works
| | |
|—|—|
| npm package | n8n-nodes-nx3 |
| n8n node | IntellX for Sage X3 (credential: IntellX for Sage X3 API) |
| Protocol | Sage X3 SOAP web services (Syracuse), CAdxWebServiceXmlCC |
| X3 side | XCHATX3OBJ sub-program, installed by the ChatX3 patch |
| Scope | Standard, vertical and custom objects and actions |
| Security | The Syracuse user’s and the folder’s function rights are enforced on every call |
Data flows directly between your n8n instance and your Sage X3 server. The
ChatX3 gateway is only contacted for license control (see
§3.3): no business data and no credential ever
transits through it.
Every call reproduces what an X3 user would do on screen. X3 runs it with
GXCHATX3OBJ=1, GWEBSERV=1, GSERVEUR=0, GBATCH=0 and GIMPORT=0, so
standard, vertical and custom processes execute as if the user had typed the
values — with their rights, their default values and their validations.
Two limits follow from that:
- An object or action that opens an extra window beyond the base screen
- Table-type objects are not handled by the ChatX3 patch yet.
- [ ] The ChatX3 patch is integrated on the X3 folder (§3.1)
- [ ] A web services pool is created, started and reachable from n8n (§3.2)
- [ ] The X3 runtime server can reach the ChatX3 license endpoint (§3.3)
- [ ] The Syracuse user has the
statusAndUsage → Readright (§3.4) - [ ] The folder’s X3 user has Web services connection = Yes (§3.5)
- [ ] The
n8n-nodes-nx3package is installed on the n8n instance (§4)
cannot be driven this way.
2. Requirements
Tick every box before the first call. Sections 3 and 4 walk through each one.
3. Sage X3 setup
3.1 ChatX3 patch
1. Download the ChatX3 patch from intellx.chat.
2. In Sage X3, open Development → Utilities → Patches → Patch integration.
3. Reproduce the checkboxes shown below, then run the integration.
!SyracuseSOAP.png”>SOAP child process configuration on Syracuse
b. Create and start a web services pool on the folder. Its alias is
what goes into the credential’s Pool Alias:
!Web services SOAP pool created and started
3.3 License endpoint
To verify the ChatX3 license, the X3 runtime server connects randomly once
or twice a week to:
https://akfcgzazfvqipbjvdemn.supabase.co
> [!IMPORTANT]
> Behind a proxy or a firewall, allow that URL outbound from the X3 runtime
> server. Without it the license check fails. Nothing else goes through it.
3.4 Syracuse user rights
The Syracuse user used by the credential needs, in its security profile:
> statusAndUsage → Read
!Syracuse security profile with the statusAndUsage read right
3.5 X3 user web services right
The folder’s X3 user linked to that Syracuse user must have:
> Web services connection → Yes
!X3 user record with web services connection enabled
4. Installing the node in n8n
4.1 From the n8n interface (community nodes)
1. Open Settings → Community nodes.
!n8n2.png”>Community nodes Install button
3. Enter n8n-nodes-nx3, tick the acknowledgement box, then click
Install.
!n8n4.png”>Searching for the Sage node in a new workflow
5. The IntellX for Sage X3 node appears with its actions.
4.2 Self-hosted Docker, from source
To load the package straight from a checkout, build it and mount it into the
container’s custom extensions folder:
npm install && npm run build
docker run -d --name n8n -p 5678:5678
-v n8n_data:/home/node/.n8n
-v /path/to/n8n-nodes-nx3:/home/node/.n8n/custom/n8n-nodes-nx3:ro
n8nio/n8n
Restart the container after every rebuild.
> A package loaded from the custom folder is registered by n8n under the type
> CUSTOM.nx3Soap (and CUSTOM.nx3SoapTool). Recent n8n versions (2.x) also
> resolve n8n-nodes-nx3.nx3Soap to that same package; on an older version, a
> workflow exported from an instance where the package came from npm shows its
> nodes with a ? icon and asks to install the package: edit the type of the
> nodes in the JSON before importing, or install from npm instead.
5. Credentials
You authenticate with your Syracuse user — the one you log into Sage X3
with — over HTTP Basic authentication.
| Field | Required | Description |
| — | — | — |
| Base URL | ✅ | Scheme + host + port of the Syracuse server, e.g. https://my-x3-host:8124. The SOAP path is appended automatically. |
| Username | ✅ | Syracuse user (§3.4) |
| Password | ✅ | Syracuse password |
| Code Language | | Default codeLang of the SOAP call context (FRA, ENG…). Defaults to FRA. |
| Pool Alias | | Default poolAlias, e.g. POOL_SEED. Must match an existing, started pool (§3.2). |
| Pool ID | | Default poolId. Usually left empty. |
| Request Config | | Default requestConfig. The node adds the JSON-mode flags on its own. |
| Allow Self-Signed Certificates | | Skip TLS validation. For on-premise X3 with a self-signed certificate only; never for production over the Internet. |
The Test button sends a real POST to the SOAP endpoint the node calls,
with an empty XCHATX3OBJ payload. Reaching X3’s answer proves the URL, the
port, the authentication and the pool all work; nothing is read or written. A
405 at this point means the Base URL is not the SOAP service — most often a
missing port (Syracuse listens on 8124 or 8443, rarely on 443).
6. Operations
Pick a Resource, then an Operation:
| Resource | Operation | X3 action | What it does |
| — | — | — | — |
| Object | Read | READ | Read a record by its identifier and return its full field tree |
| Object | List | LIST | List the records of an object, with optional selection criteria |
| Object | Create | CREATE | Create a record from a JSON payload |
| Object | Modify | MODIFY | Update fields of an existing record |
| Object | Describe | DESC | Get the screens, fields and local menus of an object class |
| Object | Custom Action | (free text) | Send any XACTION the patch exposes, including future ones |
| SQL | Analyse | ANALYSE | Check that a SQL query is valid, without returning rows |
| SQL | Select | SELECT | Execute a SQL SELECT and return the rows |
| Query | Run | READ + SELECT | Run a graphical query (Requêteur, object ALT) by its code: its definition is read, translated to SQL and executed, with the X3 bounds as parameters — see §8.3 |
| Sub-Program | Run (Advanced) | (raw XML) | Call any X3 sub-program with a raw input payload — the escape hatch |
Workflows saved with version 1 of the node keep its flat operation list
(Read Sage X3 Object, SQL Select…) and keep working unchanged; nodes added
from now on use version 2 with the resources above. The two versions run the
same code.
The Action Code field is always visible and pre-filled from the operation,
so you can see exactly which XACTION is sent, and override it if needed.
7. Object operations
7.1 Parameters
Shown for Read, List, Create, Modify, Describe and Custom:
ITM (items), BPC (customers), SOH (sales orders)…
XTRANSACTION). Leaveempty for the default one.
XIDENT). Required for Read and Modify. For Create it must match the key field in the data (e.g.
ITM0.ITMREF) unless X3 generates the key. Optional for Custom, hidden for
List and Describe. A composite key is written with ~ between its parts,
in the order of the object’s key: FIN311~NA021 for an item-site record,
SMDIV~FR0120104SMDIV000001 for a journal entry, FR012~BMS001~1~8 for a
standard cost (site, item, version, fiscal year).
XDATAJSON. Shown for Create, Modify,List and Custom.
7.2 Data JSON conventions
The payload mirrors the X3 screens: one key per screen abbreviation, and
inside it one key per field.
"TSICOD": ["20"] or "TSICOD": ["20", "21", "99"].
(n) to the field name: "TSICOD(1)": "21".NBLIG and friends): X3 fillsthem.
"YYYY-MM-DD": "SBSDAT": ["2026-05-01"]."", the integer 0, or the date "0000-00-00".In the output, single-value arrays are unwrapped to scalars by default
(Compact Single-Value Arrays, §10) — ["FR001"]
comes back as "FR001", while multi-value arrays stay arrays.
7.3 Context (optional)
An optional envelope merged into XDATAJSON as a top-level context key.
Leave it untouched and the payload is sent exactly as written.
| Option | Effect |
| — | — |
| Language | Override the XCHATX3OBJ language for this call (e.g. ITA). A different layer from the SOAP-level codeLang of the credential. |
| User | Run the call as another user (e.g. FU01). Only honored when the caller has GPROFIL=ADMIN. |
| Response Screens | Return only these screens, e.g. ITM0, ITM1. |
| Response Fields | Return only these fields, e.g. DES1AXX. |
| Include Hidden Fields | Include hidden and technical fields, excluded by default. |
Resulting payload:
{
"context": {
"language": "ITA",
"user": "FU01",
"response": { "screens": ["ITM0"], "fields": ["DES1AXX"], "hidden_fields": true }
},
"ITM0": { "DES1AXX": ["New description"] }
}
7.4 Read
Reads one record. Example on the SEED folder — object GAS, transaction
1PAGE, identifier SMDIV~FR0120104SMDIV000001, no data:
{
"success": true,
"action": "READ",
"object": "GAS",
"ident": "SMDIV~FR0120104SMDIV000001",
"transaction": "1PAGE",
"data": {
"HAE1": {
"TYP": "SMDIV",
"NUM": "FR0120104SMDIV000001",
"CPY": "FR10",
"FCY": "FR012",
"JOU": "ODDIV",
"ACCDAT": "2024-04-01",
"CUR": "EUR",
"NUMLIG": [1, 2],
"ACC1": ["608801", "486000"],
"ACCDES": ["Insurance invoiced", "Prepaid expenses"],
"AMT1": [1000, 1000],
"TOTDEB": 1000,
"TOTCDT": 1000,
"NBLIG": 2
}
},
"messages": [{ "trace": "F64843" }],
"trace": "F64843",
"sessionId": "6784"
}
7.5 List
Reads the object’s main left list through an advanced selection. The payload
takes two optional keys:
{
"list_nb": 10,
"list_filters": "[F:BPA]CRY<>"FR" & [F:SOH]CUSORDREF<>"""
}
list_nb — number of rows to return. Default: the folder’s GNBGAUCHEparameter, or 100.
list_filters — an X3 selection expression on any field of the main table, in [F:TABLE]FIELD syntax. It is added to the object’s own filters, which
are echoed back under filters.
The output carries the column codes and titles once, and the rows as arrays:
{
"table_count": 1193,
"list_count": 10,
"filters": {
"CRITERE(0)": "1=1",
"FILTSUP(0)": "SOHCAT < 4",
"FILRAP(0)": "[F:BPA]CRY<>"FR" & [F:SOH]CUSORDREF<>"""
},
"codes": ["[F:SOH]SOHNUM", "[F:SOH]BPCORD", "[F:SOH]ORDDAT", "[F:SOH]CUSORDREF", "[F:SOH]DLVSTA"],
"titles": ["Order no.", "Order customer", "Order date", "Reference", "Delivery status"],
"values": [
["SOWDE0120012", "DE001", "2025-07-21", "WEB015182112", 1],
["SOWDE0120011", "DE001", "2025-12-21", "WEB015182112", 2]
]
}
tablecount is the size of the whole table, listcount the number of rows
returned. To get one n8n item per row, split on data.values and map the
positions through data.codes.
7.6 Create
Creates a record. Example on SEED — a sales quote, object SQH:
{
"SQH0": { "BPCORD": ["FR001"], "SALFCY": ["FR021"], "SQHTYP": ["SQN"] },
"SQH1": { "STOFCY": ["FR021"], "VACBPR": ["FRA"] },
"SQH2": { "GROPRI": [1000], "ITMREF": ["ASS001"], "QTY": [1] }
}
X3 answers with the record as saved — including the fields it computed, such
as the generated number — and its user messages:
{
"success": true,
"action": "CREATE",
"object": "SQH",
"data": {
"SQH0": {
"SQHNUM": "FR0212605SQN00000001",
"SQHTYP": "SQN",
"BPCORD": "FR001",
"BPCNAM": "Urban Cycle",
"SALFCY": "FR021",
"CUR": "AED",
"QUODAT": "2026-05-28"
}
},
"messages": [
{ "trace": "F64842" },
{ "info": "Object created: FR0212605SQN00000001" },
{ "alert": "Access not allowed on this item (Elaboration)" }
],
"trace": "F64842",
"sessionId": "6784"
}
Note the alert: X3 can report success: true and still carry warnings, or
even an error. See §9 for how to gate on them.
7.7 Modify
Updates fields of an existing record. Example on SEED — object SOH,
transaction STD, identifier SOWFR0110003, quantity of line 1 set to 7:
{
"SOH4": { "QTY(1)": [7] }
}
{
"success": true,
"action": "MODIFY",
"object": "SOH",
"ident": "SOWFR0110003",
"data": {
"SOH0": { "SOHNUM": "SOWFR0110003", "SOHTYP": "WEB", "BPCORD": "FR004", "BPCNAM": "Micro Systems", "SALFCY": "FR011", "CUR": "EUR", "ORDDAT": "2026-05-22" },
"SOH4": { "ITMREF": ["DIS009", "DIS007"], "QTY": [1, 7], "GROPRI": [220, 250], "NBLIG": 2 }
},
"messages": [
{ "trace": "F64844" },
{ "alert": "Do you want to recalculate prices and discounts? (No)" }
],
"trace": "F64844",
"sessionId": "6784"
}
A question X3 would ask on screen is answered with its default and reported as
an alert, as above.
> X3 keeps a lock on a record read through the same session. To chain a Read
> and a Modify on one record, pass the Read’s sessionId to the Modify’s
> Pool ID Override (§12), or skip the Read.
7.8 Describe
Returns the metadata of the object class: its screens, each with the fields
(name, type, length, dimension, required flag, description, local menu number)
and blocks, then the local menus used, with their lines. It is what an AI Agent
should call before a Create to learn the payload shape.
{
"screens": {
"screen_code": "ACV2",
"screen_name": "Activity codes",
"fields": [
{ "name": "CODACT", "system_type": "Char", "length": 5, "dimension": 1, "required": true, "description": "Activity code", "type": "ACV" },
{ "name": "FLACT", "menunumber": 1, "systemtype": "Integer", "description": "Active", "type": "M" }
],
"blocks": [{ "code": "10", "description": "Identifier", "type": "List" }]
},
"local_menus": [
{ "menunumber": 1, "menuname": "No/Yes", "lines": [{ "linenumber": 1, "message": "No" }, { "linenumber": 2, "message": "Yes" }] }
]
}
7.9 Custom action and Run Sub-Program
Custom Sage X3 Action sends the object code, identifier and data with any
XACTION you type — for actions the ChatX3 patch exposes beyond the five
above, including ones added by a later patch version.
Run Sub-Program (Advanced) bypasses XCHATX3OBJ altogether: you name the
public sub-program and pass its raw input XML (wrapped in CDATA for you). The
output is the generic envelope of §9 with the sub-program’s
result, for anything the object layer does not cover.
8. SQL operations
SQL Analyse validates a query without running it; SQL Select runs it
and returns the rows. Both accept optional Max Lines and **Max Time
(Seconds)** caps, sent to X3 as context.maxlines and context.maxtime.
> Max Time caps the execution inside X3; Request Timeout in Advanced
> Options caps the HTTP call (30 s by default). Keep the former below the
> latter, or the request gives up before X3 does.
Queries are written in the dialect of the database behind X3 — T-SQL on
Microsoft SQL Server, PL/SQL on Oracle.
8.1 Query parameters
Rather than concatenating values into the query — unsafe, above all when an AI
Agent supplies them — declare them under Query Parameters and reference
them with {{name}}:
SELECT TOP (100) [USR0], [CREUSR0], [CREDAT_0]
FROM [AUTILIS]
WHERE USR_0 IN ({{logins}})
AND CREDAT_0 BETWEEN {{from}} AND {{to}}
[[AND CREUSR_0 = {{creator}}]]
literal: never wrap {{name}} in quotes yourself.
[[ ... ]] marks an optional clause: dropped entirely when its parameters are empty, kept otherwise. Ideal for filters an agent may or may
not fill in.
{{name}} used outside an optional clause with no value raises a clearerror rather than silently changing the query.
-- line comment, a / ... / block or a string literal are left untouched, so commenting a
filter line out works exactly as a SQL reader expects.
| Type | Rendering | Use for |
| — | — | — |
| Text | 'value', embedded quotes doubled | =, or a LIKE where you write the pattern yourself |
| Contains | '%value%', with % _ [ inside the value escaped | LIKE {{name}} — type dupont, not %dupont% |
| Number | bare, validated numeric | >, <, >= |
| Date | quoted, YYYY-MM-DD or ISO-8601 | BETWEEN, comparisons |
| Boolean | 1 / 0 | flags |
| List | 'a','b','c' — comma-split, each quoted | IN ({{name}}) |
> Contains escapes the LIKE wildcards inside the value: searching for
> remise 50% looks for that exact text instead of everything starting with
> remise 50.
> Use List, not Text, for IN ({{name}}). A Text value holding a,b
> becomes the single literal 'a,b' and matches nothing; the node detects that
> case and tells you to switch type. A List value cannot itself contain a
> comma, since the comma is the separator.
#### Parameters as JSON
Parameters Mode picks where the values come from. Fields is the list
above. JSON takes the whole list in one field, as an array of
{ "name", "type", "value" } objects, which is what you want when the query
and its parameters are decided upstream — a catalogue of queries, a generic
sub-workflow, a Code node that builds the filters:
SQL Query: ={{ $json.sqlQuery }}
Parameters Mode: JSON
Query Parameters (JSON): ={{ $json.params }}
with, on the incoming item:
{
"sqlQuery": "SELECT … WHERE 1 = 1 [[AND BPCNUM0 IN ({{customer}})]] [[AND CREDAT0 >= {{from}}]]",
"params": [
{ "name": "customer", "type": "list", "value": ["FR001", "FR002"] },
{ "name": "from", "type": "date", "value": "2025-01-01" }
]
}
value may be a string, a number, a boolean, null or an array (joined with
commas for a List). The same escaping and [[ ... ]] rules apply; an unknown
type is an error rather than a silent fallback to Text. The template in
§12 builds a whole MCP server on this mode.
8.2 Simplify and Row Format
Sage X3 returns SQL rows with generic keys — Col1, Col2… — losing the
link to the selected columns. Turn on Simplify (SQL Select only) to get the
rows with their real names restored from the query:
{
"rowCount": 3,
"columns": ["USR0", "CREUSR0", "CREDAT_0"],
"rows": [{ "USR0": "ADMCA", "CREUSR0": "ADMIN", "CREDAT_0": "2015-08-14" }]
}
The envelope shrinks to the data itself: success, messages and trace
only appear when something went wrong, so a failure is never silent while the
happy path stays cheap to read — which matters when the rows feed an AI Agent.
When the row count reaches Max Lines, truncated: true is added: Sage X3
stops at the cap without saying so, and the flag is how a workflow or an agent
tells a complete result from a slice.
Dates come back as plain YYYY-MM-DD, and X3's 0000-00-00 "not set"
sentinel becomes null. The time part is dropped only when it is exactly
midnight UTC, so a genuine timestamp keeps its full value.
Row Format then picks the row shape:
{{ $json.rows[0].USR_0 }}, and rendered natively by the n8n Table view.
columns. Far more compact: theright choice when the rows go to an AI Agent.
Column names are read after SQL comments are stripped, so a query may open with
a / ... / documentation header and carry comments between select items;
string literals are left alone. The renaming is best-effort and never reorders
or drops data: with SELECT *, an unresolvable expression or any count
mismatch, the original Col_N keys are kept. Leave Simplify off to get the
full envelope of §9.
8.3 Run Sage X3 Query: an X3 graphical query as SQL
The Run Sage X3 Query operation takes the code of a Sage X3 graphical
query (object ALT, the Requêteur graphique): it reads the definition from
X3, renders it as a SQL SELECT, runs it through the same path as SQL and returns the rows. A query a key user designed in X3 thus becomes
Select
one n8n node, with the X3 bounds screen as parameters, ready to be scheduled
or handed to an AI Agent without rewriting it by hand.
A query is stored as an X3PORTAL XML document in ALT1.CLBFL: the tables and
their aliases, the links between them, and the columns dropped on the grid.
The selection criteria sit next to it in ALT2.SEL, written in the X3 4GL
expression language. The node turns all of it into SQL:
| X3 | SQL | Anything the node cannot translate (an X3 global variable such as Parameters tool ( back. Off: the node returns the SQL and the query structure only. To value, both inclusive, either side may stay empty. Name the column by - Bounds as Parameters — when Execute is off, keep an optional (code language, pool, public name, request timeout) and *Include Request in Output With Execute on, the same shape as Object operations return one item per input item, shaped as: | Field | Description | > X3 may report SQL Select with Simplify on returns the reduced shape of §8.2 instead. On a transport or HTTP failure the node raises a one-sentence message naming All optional. Output-shaping options default to on; debug and override | Option | Default | Purpose | The node has The Query Sage X3 in natural language with an AI agent and an MCP server but the model composes the SQL itself and can reach any table. with branch of the same workflow, re-entered through an Execute Workflow Trigger, So end every parameter that backs an optional filter with an empty default: The red asterisk next to the field in the tool's Test dialog disappears once Every byte of a tool result is paid for in tokens, on every call: question, not for the table. a confirmation on the conversational path; a direct MCP call bypasses it. Read an item Modify an item description data Read → Modify on the same X3 session (keep the lock) 1. The List → one item per row 1. A generic SQL MCP server from a query catalogue stores its queries and WHERE filters in a JSON catalogue, runs them all through Filtered SQL Select drop its clause. Run an X3 graphical query (Requêteur) with bounds Bound: column Turn Execute off to see the SQL and the structure instead, and feed the | Symptom | Likely cause | When reporting a problem, include the X3 trace name returned in as required for n8n Cloud verification. ChatX3 Before opening an issue, check the requirements and attach The full history is in CHANGELOG.md. Simplify; numeric caps accepted from string expressions; generic MCP template failures explained with the URL called; comment. Select with Query Parameters and Simplify, Request Timeout and readable renamed IntellX for Sage X3. output shaping; session affinity; --- IntellX for Sage X3 — Sage X3 integration for n8n
| --- | --- |
| |
FROM [SQUOTE] SQH |
| | LEFT JOIN [BPSUPPLIER] BPS ON ITP.BPSNUM0 = BPS.BPSNUM0 (I inner, R right; several links on the same pair merge into one ON) |
| [F:SQH]BPCNAM(1) | SQH.BPCNAM_1 |
| | SQH.BPCORD_0 AS [Customer] |
| Expr="=+1" | 1 AS [Nombre] (formula column) |
| &, |, ! | AND, OR, NOT |
| "text", 'text' | 'text' |
| [0/0/0], [31/12/2025] | '1753-01-01', '2025-12-31' (Oracle: DATE '1599-12-31') |
| date$, datetime$ | CAST(GETDATE() AS date), GETDATE() (Oracle: TRUNC(SYSDATE), SYSDATE) |
| left$, right$, mid$, len, toupper, tolower, num$, val, int, abs, year, month, day, gdat$, string$, instr, pat | the matching SQL Server or Oracle function |
| find([F:ORD]WIPTYP, 2, 7) | ORD.WIPTYP_0 IN (2, 7) |
| [F:ADI]CODE = 20 ; [F:ITM]TSICOD (link expression of the Links tab, ALT2.LNK) | LEFT JOIN [ATABDIV] ADI ON ADI.NUMTAB0 = 20 AND ADI.CODE0 = ITM.TSICOD_0, the key fields coming from index CODE of the table |
| DLVOSTL, NYTBILL(2) (field of the main table, no prefix) | MVC.DLVOSTL0, MVC.NYTBILL2 |
| addmonth(date$, -12) | DATEADD(month, -12, CAST(GETDATE() AS date)) (Oracle: ADD_MONTHS) |
| [F:STL]SHLDAT - date$, date$ + 30 | DATEDIFF(day, CAST(GETDATE() AS date), STL.SHLDAT_0), DATEADD(day, 30, ...) (Oracle keeps the native arithmetic) |
| GACTX.LAN, GLANGUE | the credential language, e.g. 'FRA' |
| GACTX.USER, GUSER | the X3 User option, when set |
| and, or, not | AND, OR, NOT |
| [ITM]DES1AXX (translatable text, X3 type AX*) | ISNULL((SELECT TOP (1) TEXTE0 FROM [ATEXTRA] WHERE CODFIC0 = 'ITMMASTER' AND ZONE0 = 'DES1AXX' AND LANGUE0 = 'FRA' AND IDENT10 = ITM.ITMREF0 AND IDENT2_0 = ''), '') — these fields have no column of their own; the language is the credential's |
| [SQH]QUOSTA (local menu, X3 type M) | ISNULL((SELECT TOP (1) LANMES0 FROM [APLSTD] WHERE LAN0 = 'FRA' AND LANCHP0 = 430 AND LANNUM0 = SQH.QUOSTA_0), '') — the label, with the menu number read from the query definition; see the Local Menus option |
| Sort="A" / "D" | ORDER BY, by break level then column order |
| MAXLIG (Max lines) | TOP (n) (Oracle: FETCH FIRST n ROWS ONLY) |
GUSER, a
func call, an unknown function) is left as-is in the SQL and listed in
warnings, so the result is never silently wrong.FRVEN055). The node reads the ALT record itself.
its field (BPCORD), by alias and field (SQH_BPCORD) or by its grid label
(Customer). Only the columns the query marks as bounded (Bounds="Y") are
accepted; an unknown name fails with the list of bounded columns. Values are
escaped by type (date, number, text, from the X3 data types), never
concatenated raw.
[[AND col >= {{ALIASFIELDfrom}}]] / [[AND col <= {{ALIASFIELDto}}]]
clause per bounded column, in the syntax of §8.1,
with the typed parameter list under parameters. Always on when Execute
is on.
- Column Aliases — Label (the grid label, [Customer name]), Code
(SQHBPCNAM, formulas as EXPRn) or None.
- Include Hidden Columns — keep the Vis="N" columns in the SELECT.
- Include Query Structure — add tables, joins, columns, criteria and
order by next to the rows.
- Include Table Fields — add every field and index of each table.
- Max Lines — -1 keeps the query's own cap, 0 removes it, any other
value replaces it; also sent to X3 as max_lines.
- Local Menus — how a local-menu column (quote status, item status...)
comes back: Label (default, the text in the credential language, as
the X3 grid shows it), Code (the stored number) or Both (the label,
plus the number in a column named after the label followed by
(code)). Criteria, bounds and sort always work on the number.
- Max Time (Seconds) — the X3 max_time cap.
- Row Format — Objects (default) or Arrays, as in
§8.2.
- Schema — folder prefix for table names, e.g. SEED or basex3.SEED.
Not needed when the SQL runs through X3, which is already in the folder.
- SQL Dialect — SQL Server (default) or Oracle.
- X3 User — the X3 user code substituted for GUSER / GACTX.USER in
the criteria of "my ..." queries (my quotes, my order book). Left empty,
the criterion is reported in warnings and the SQL fails.
Output*, which here describes the SQL call and the duration of each call.SQL Select with Simplify, plus the query
identity and the SQL that was run:{
"code": "FRVEN055",
"title": "Open Quotes",
"rowCount": 12,
"columns": ["Customer", "Customer name", "Sales site", "Quote date", "Quote no."],
"rows": [{ "Customer": "FR001", "Customer name": "Urban Cycle", "Sales site": "FR011", "Quote date": "2026-09-02", "Quote no.": "SQHFR0110042" }],
"sql": "SELECTn SQH.BPCORD0 AS [Customer],n ...nFROM [SQUOTE] SQHnWHERE (SQH.QUOSTA0 = 1)n AND SQH.BPCORD0 >= 'FR'nORDER BY SQH.BPCORD0 ASC"
}
truncated: true appears when the row count reaches the cap, warnings when
part of the query could not be translated. With Execute off, the structure:{
"code": "FRVEN055",
"title": "Open Quotes",
"objectLink": "SQH",
"functionLink": "EXEALH",
"tables": [{ "alias": "SQH", "name": "SQUOTE", "label": "Quote header", "sql": "[SQUOTE]" }],
"joins": [],
"columns": [
{ "position": 1, "label": "Customer", "expression": "[SQH]BPCORD", "sql": "SQH.BPCORD_0",
"alias": "Customer", "visible": true, "sort": "asc", "level": 1, "sum": false, "bounds": true, "type": "BPR" }
],
"criteria": [{ "x3": "[F:SQH]QUOSTA=1", "sql": "SQH.QUOSTA_0 = 1" }],
"orderBy": [{ "sql": "SQH.BPCORD_0", "direction": "asc" }],
"parameters": [],
"sql": "SELECTn SQH.BPCORD0 AS [Customer],n ...nFROM [SQUOTE] SQHnWHERE ...nORDER BY SQH.BPCORD0 ASC",
"warnings": []
}
sum: true marks a column the X3 grid totals at each break; the SQL keeps the
detail rows, as the grid does, and leaves the aggregation to you. level is
the break level of the column.9. Output
{
"success": true,
"status": 1,
"action": "READ",
"object": "ITM",
"ident": "ASS001",
"transaction": "",
"data": {
"ITM0": { "ITMREF": "ASS001", "DES1AXX": "Computer server", "TCLCOD": "BMSOL" },
"ITM1": { "EANCOD": "3782940199614", "TSICOD": ["10", "12", "122"] }
},
"messages": [{ "trace": "F1730" }],
"trace": "F1730",
"sessionId": "6784"
}
| --- | --- |
| success | true when X3 reports the action succeeded (XRETURNJSON.success, fallback status === 1). |
| status | Raw X3 status: 1 OK, 0 failure. |
| action / object / ident / transaction | Echo of the request, for traceability. |
| data | The object payload (XDATAJSON), parsed into a navigable object. |
| messages | X3 user messages — info, error, alert — and the trace entry. |
| trace | The X3 trace name. A standard X3 trace is always written; quote it in a support request. |
| sessionId | The X3 pool entry that handled the call. Pass it back as Pool ID Override to chain calls on the same session. |
| technicalInfos | Pool and timing metrics (poolExecDuration, totalDuration…). Only present when Include Technical Infos is on. |success: true and still include an error message (an
> access-level check, for instance). To gate a workflow strictly, test the
> messages in an IF node: {{ !($json.messages || []).some(m => m.error) }}
the cause and the URL called (timeout, connection refused, unknown host, 401,
403, 404, 405, 407, 502-504). With Continue on Fail the item carries
error, httpStatus and url instead of stopping the workflow.10. Advanced options
options default to off.
| --- | --- | --- |
| Compact Single-Value Arrays | on | Unwrap single-element arrays: ["Iconext"] → "Iconext". Multi-value arrays stay arrays. |
| Trim Trailing Empty Values | on | Drop the trailing empty values X3 pads dimensioned fields with: ["A","",""] → ["A"]. |
| Request Timeout (Seconds) | 30 | Abort the SOAP call when X3 does not answer, instead of hanging. |
| Code Language Override | — | Override the credential codeLang for this call. |
| Pool Alias Override | — | Override the credential poolAlias for this call. |
| Pool ID Override | — | Pin the call to a given X3 session, e.g. ={{ $json.sessionId }} from a previous step, to keep a record lock across Read → Modify. |
| Public Name Override | — | Call another sub-program than XCHATX3OBJ. |
| Include Request in Output | off | Add request — the URL called, the JSON payload and the SOAP envelope sent — for debugging. |
| Include Result in Output | off | Add result, the parsed X3 result payload, for debugging. |
| Include Technical Infos | off | Add technicalInfos, the X3 pool and timing metrics. About a quarter of each item; keep it off when the node feeds an AI Agent. |
| Include Raw SOAP Response | off | Add raw, the full SOAP envelope as XML, for transport-level debugging. |11. AI Agent and MCP
usableAsTool enabled: attach it to an AI Agent node for
object operations ("find item BMS009", "create a customer") as well as SQL
Select. n8n generates the tool variant on its own — n8n-nodes-nx3.nx3SoapTool
in workflow JSON — with a Description field the model reads to pick the
tool. The same tool nodes can be wired to an MCP Server Trigger to expose
them to Claude, Cursor or any MCP client.
template on n8n.io is a complete agent + MCP setup built on this node.11.1 Two ways to wire a SQL tool
$fromAI on SQL Query. Flexible,[[ ... ]] optional clauses and put $fromAI on the Value of each
Query Parameter. The model cannot write SQL, values are escaped by the node,
and unfilled filters disappear on their own. Prefer this one.
receives a query name and a filters object, looks the query up
in a JSON catalogue, validates the filters, and calls one SQL Select node in
Parameters Mode: JSON. The agent and the MCP clients see two tools,
x3querycatalog and x3queryrun, whatever the size of the catalogue.
Adding a query is one JSON entry, and adds no tool.
This is the design of the template in
templates/sage-x3-sql-mcp/.11.2 Make optional filters actually optional
$fromAI('name', 'description', 'string') produces a mandatory field: n8n
only marks it optional when the call carries a fourth argument, the default
value. Without it the model is forced to supply a value on every call, an
[[ ... ]] clause can never be dropped, and the call is rejected before it
reaches the node with Received tool input did not match expected schema.={{ $fromAI('creator', 'Creator code. Leave empty to skip this filter', 'string', '') }}
it is right. Keep the default off for genuinely required inputs — reading a
record without its identifier makes no sense.11.3 Keep tool calls cheap
maxLines cap sized for the
Expose only what you mean to.12. Workflow recipes
Read Sage X3 Object, object ITM, identifier ASS001.Modify Sage X3 Object, object ITM, identifier ASS001,{ "ITM0": { "DES1AXX": ["New description"] } }.Read Sage X3 Object node returns sessionId.
2. In the following Modify Sage X3 Object node, set **Advanced Options →
Pool ID Override** to ={{ $('Read Sage X3 Object').item.json.sessionId }}.List Sage X3 Objects on ICD with data {"list_filters": "[F:ITC]FIYNUM >= 8"}.
2. A Split Out node on data.values.
3. A Read Sage X3 Object whose identifier maps the positions, e.g.
={{ $json['1'] }}~{{ $json['0'] }}~1~{{ $json['6'] }}.templates/sage-x3-sql-mcp/
one runner nested in the same workflow in Parameters Mode: JSON, and exposes only **five
tools** on an AI Agent and an MCP Server: x3querycatalog and
x3queryrun for every query, x3_object (describe, list, read) and
x3objectwrite (create, modify) through an object runner, and
x3objectguide. Adding a query is one JSON entry and one SQL file, and
adds no tool.SQL Select, Simplify on, query: SELECT TOP (200) [SOHNUM0], [BPCORD0], [ORDDAT_0]
FROM [SORDER]
WHERE 1 = 1
[[AND [BPCORD_0] = {{customer}}]]
[[AND [ORDDAT_0] >= {{from}}]]
customer (Text) and from (Date): leave either empty toRun Sage X3 Query, Query Code FRVEN055, Execute on, oneBPCORD, from FR, to FRZZZ.
sql to a SQL Select node if you want to edit it by hand first.13. Troubleshooting
| --- | --- |
| 405 Method Not Allowed at credential test or at run time | The Base URL is a web server, not the SOAP service — most often the port is missing (:8124 or :8443). |
| 401 / 403 | Wrong Syracuse password, or the user is not allowed to call web services (§3.4, §3.5). |
| self-signed certificate | Enable Allow Self-Signed Certificates in the credential. |
| Timed out / connection refused / host not found | X3 is unreachable: check the Base URL and the network; raise Request Timeout if the server is merely slow. |
| No Pool: | The pool is not started or the alias is wrong (§3.2). |
| Insufficient access level / Niveau d'accès insuffisant | The Syracuse user lacks function rights on the object. |
| Modification in progress on another workstation | A previous Read still holds the record lock. Chain with Pool ID Override, or skip the Read. |
| Erreur zone [M:...] | Field validation failed: check that the identifier matches the key field in the data, the value formats, and the counters. |
| SQL Select returns 0 rows with an IN filter | The parameter is typed Text instead of List. |
| Imported nodes show a ? icon and n8n asks to install n8n-nodes-nx3 | Type mismatch between npm install (n8n-nodes-nx3.nx3Soap) and custom-folder install (CUSTOM.nx3Soap). Edit the node type in the JSON (§4.2). |
| Received tool input did not match expected schema | A $fromAI without a fourth argument makes the field mandatory (§11.2). |
| The license check fails | The X3 runtime server cannot reach the license endpoint (§3.3). |trace.14. Compatibility
CAdxWebServiceXmlCC, Apache Axis 1.4 SOAP stack) with theXCHATX3OBJ sub-program.15. Support and resources
the X3 trace name.16. Version history
truncated flag on
with a query catalogue and a nested runner, everything in one workflow.technicalInfos now opt-in.{ rowCount, columns, rows }; SQL dates as YYYY-MM-DD, 0000-00-00 as
null.
network errors.usableAsTool.