@fluentcommerce/fluent-mcp-extn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1810 @@
1
+ # Tool Reference
2
+
3
+ This document describes every MCP tool exposed by `fluent-mcp-extn`, including
4
+ purpose, required inputs, and example requests.
5
+
6
+ ## Response envelope conventions
7
+
8
+ ### Success
9
+
10
+ ```json
11
+ {
12
+ "ok": true
13
+ }
14
+ ```
15
+
16
+ Most tools include extra fields like `event`, `response`, `job`, or `status`.
17
+
18
+ ### Failure
19
+
20
+ ```json
21
+ {
22
+ "ok": false,
23
+ "error": {
24
+ "code": "VALIDATION_ERROR",
25
+ "message": "Invalid tool arguments.",
26
+ "retryable": false,
27
+ "details": {}
28
+ }
29
+ }
30
+ ```
31
+
32
+ `error.retryable` indicates whether a caller can safely retry the same request. Non-idempotent writes intentionally avoid automatic retry behavior.
33
+
34
+ ## `config.validate`
35
+
36
+ Validates configuration and auth readiness without calling Fluent APIs.
37
+
38
+ - **Input**: no fields
39
+ - **Use when**: startup checks, CI preflight, environment validation
40
+
41
+ Example:
42
+
43
+ ```json
44
+ {}
45
+ ```
46
+
47
+ ## `health.ping`
48
+
49
+ Returns server health, SDK availability, and safe config summary.
50
+
51
+ - **Input**: no fields
52
+ - **Use when**: quick readiness checks in clients/agents
53
+
54
+ Example:
55
+
56
+ ```json
57
+ {}
58
+ ```
59
+
60
+ ## `event.build`
61
+
62
+ Builds a Fluent event payload only (no API call).
63
+
64
+ - **Required**: `name`, `entityRef`, `entityType`
65
+ - **Optional**: `entityId`, `rootEntityId`, `rootEntityType`, `source`, `type`, `attributes`, etc.
66
+ - **Note**: if available in your flow, provide both `entityRef` and `entityId`.
67
+
68
+ Example:
69
+
70
+ ```json
71
+ {
72
+ "name": "ORDER_CREATED",
73
+ "entityRef": "ORD-1001",
74
+ "entityId": "12345",
75
+ "entityType": "ORDER",
76
+ "attributes": {
77
+ "channel": "web"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## `event.send`
83
+
84
+ Builds and sends an event via SDK adapter.
85
+
86
+ - **Required**: `name`, `entityRef`, `entityType`
87
+ - **Optional**: all `event.build` fields plus:
88
+ - `mode`: `async` or `sync`
89
+ - `dryRun`: boolean
90
+ - **Important**: `retailerId` must match the target entity's retailer for real sends.
91
+
92
+ Example dry-run:
93
+
94
+ ```json
95
+ {
96
+ "name": "ORDER_CREATED",
97
+ "entityRef": "ORD-1001",
98
+ "entityId": "12345",
99
+ "entityType": "ORDER",
100
+ "dryRun": true
101
+ }
102
+ ```
103
+
104
+ Example send:
105
+
106
+ ```json
107
+ {
108
+ "name": "ORDER_CREATED",
109
+ "entityRef": "ORD-1001",
110
+ "entityId": "12345",
111
+ "entityType": "ORDER",
112
+ "mode": "async",
113
+ "dryRun": false
114
+ }
115
+ ```
116
+
117
+ ## `event.get`
118
+
119
+ Gets one event by ID via SDK-native `getEventById(eventId)`:
120
+
121
+ - endpoint: `/api/v4.1/event/{id}`
122
+ - **Required**: `eventId`
123
+
124
+ Example:
125
+
126
+ ```json
127
+ {
128
+ "eventId": "7815ee32-5985-485f-863a-2643e57b64a2"
129
+ }
130
+ ```
131
+
132
+ Typical success shape (truncated):
133
+
134
+ ```json
135
+ {
136
+ "ok": true,
137
+ "event": {
138
+ "id": "7815ee32-5985-485f-863a-2643e57b64a2",
139
+ "name": "ORDER_CREATED",
140
+ "entityRef": "ORD-1001",
141
+ "entityType": "ORDER",
142
+ "retailerId": "5"
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## `event.list`
148
+
149
+ Lists/filter events via SDK-native `getEvents(params)`:
150
+
151
+ - endpoint: `/api/v4.1/event`
152
+ - **Canonical filters**:
153
+ - `eventId`, `name`, `category`, `retailerId`
154
+ - `eventType`, `eventStatus`, `from`, `to`
155
+ - `start`, `count`
156
+ - `context.rootEntityType`, `context.rootEntityId`, `context.rootEntityRef`
157
+ - `context.entityType`, `context.entityId`, `context.entityRef`, `context.sourceEvents`
158
+ - **Alias filters** (mapped internally):
159
+ - `id` -> `eventId`
160
+ - `entityRef` -> `context.entityRef`
161
+ - `entityType` -> `context.entityType`
162
+ - `type` -> `eventType`
163
+ - **`context.sourceEvents` handling**:
164
+ - accepts array input in tool arguments
165
+ - serialized for query-string delivery to Event API
166
+
167
+ Example:
168
+
169
+ ```json
170
+ {
171
+ "eventType": "ORCHESTRATION_AUDIT",
172
+ "eventStatus": "FAILED",
173
+ "context.rootEntityType": "ORDER",
174
+ "context.rootEntityRef": "ORD-1001",
175
+ "retailerId": "5",
176
+ "from": "2026-02-01T00:00:00.000Z",
177
+ "to": "2026-02-15T23:59:59.999Z",
178
+ "count": 25,
179
+ "start": 1
180
+ }
181
+ ```
182
+
183
+ Typical success shape (truncated):
184
+
185
+ ```json
186
+ {
187
+ "ok": true,
188
+ "events": {
189
+ "start": 1,
190
+ "count": 25,
191
+ "hasMore": true,
192
+ "results": [
193
+ { "id": "event-id-1", "name": "ORDER_CREATED" }
194
+ ]
195
+ }
196
+ }
197
+ ```
198
+
199
+ If `event.send` does not return an `id`, use `event.list` with `name`,
200
+ `entityRef`, and `retailerId` to resolve the latest event ID, then call
201
+ `event.get`.
202
+
203
+ ## `event.flowInspect`
204
+
205
+ One-call runtime forensics for any root entity.
206
+
207
+ - **Required**: `rootEntityRef`
208
+ - **Optional**:
209
+ - `rootEntityType`: optional entity type filter (for example `ORDER`, `FULFILMENT`, `LOCATION`, `WAVE`, `PRODUCT`)
210
+ - `rootEntityId`: optional root entity ID (helps disambiguate reused refs)
211
+ - `compact`: return pre-analyzed summary instead of raw arrays (default `true`). Compact mode returns ~2-3k tokens with an `analysis` section; set to `false` for full ~24k raw data.
212
+ - `from`, `to`: optional time window
213
+ - `maxPages`: page cap per event type (`1..50`, default `10`)
214
+ - `includeEventDetails`: include compact orchestration event rows (default `true`, full mode only)
215
+ - `includeAuditDetails`: include compact audit action samples (default `false`, full mode only)
216
+ - `includeScheduled`: fetch SCHEDULED events separately (default `true`)
217
+ - `inspectStatuses`: statuses to drill via `event.get` (defaults: `["NO_MATCH", "PENDING", "FAILED"]`)
218
+ - `maxDrilldowns`: cap on individual event.get calls (`0..100`, default `50`)
219
+ - `actionSampleLimit`: max rows for action-derived sections (`1..200`, default `100`)
220
+ - **Default-on flags:**
221
+ - `compact`: pre-analyzed summary response (default `true`)
222
+ - `includeExceptions`: rule exceptions with class, message, ruleset context (default `true`)
223
+ - `includeNoMatchDetails`: enhanced NO_MATCH diagnostics from ruleSet audit events with closeMatches (default `true`)
224
+ - **Opt-in flags (default false):**
225
+ - `includeRuleDetails`: per-rule execution trace with class name, props, and timing (durationMs)
226
+ - `includeCustomLogs`: custom plugin log messages (LogCollection from CUSTOM category)
227
+ - `includeSnapshots`: entity state snapshots at each processing point
228
+ - `includeCrossEntity`: fetch child entity events (FULFILMENT_CHOICE, FULFILMENT) using rootEntityRef
229
+
230
+ **Compact mode (`compact: true`, the default):**
231
+
232
+ Returns a pre-analyzed summary with:
233
+ - `analysis.statusFlow`: ordered unique statuses seen (e.g., `["CREATED", "BOOKED", "SHIPPED"]`)
234
+ - `analysis.findings[]`: anomaly detection — NO_MATCH (CRITICAL), FAILED/webhook errors/exceptions (HIGH), PENDING/slow rulesets (MEDIUM)
235
+ - `analysis.failedWebhookEndpoints`: URLs that returned >= 400
236
+ - `analysis.slowestRulesets`: top 3 by duration
237
+ - `analysis.timespan`: first/last timestamps with durationMs
238
+ - `audit.webhookActions`: only failures (responseCode >= 400)
239
+ - `audit.mutationActions`: top 5 by queryName (name + count only)
240
+ - `audit.sendEventActions`: count + scheduledCount only
241
+ - `diagnostics.inspectedEvents`: summary (id, name, status, closeMatchCount)
242
+
243
+ **Full mode (`compact: false`):**
244
+
245
+ Returns complete raw arrays:
246
+ - Orchestration timeline counts (`status`, `entityType`, top names)
247
+ - Audit category/action breakdown with ruleset durations
248
+ - Mutation payload evidence from ACTION audit (`DynamicUpdateMutation` request blocks)
249
+ - Webhook diagnostics (`Request Endpoint`, `Response code`, `Response Body`, headers)
250
+ - SendEvent payloads and future-dated scheduling evidence
251
+ - Rule exceptions (when `includeExceptions: true`)
252
+ - NO_MATCH closeMatches with mismatch reasons (when `includeNoMatchDetails: true`)
253
+ - Per-rule execution with props/timing (when `includeRuleDetails: true`)
254
+ - Custom plugin logs (when `includeCustomLogs: true`)
255
+ - Entity snapshots (when `includeSnapshots: true`)
256
+ - Cross-entity events with full events array (when `includeCrossEntity: true`)
257
+ - `NO_MATCH`/`PENDING`/`FAILED` drilldown with full event payload via `event.get`
258
+ - Auto-recommendations based on findings
259
+
260
+ Example (compact — recommended first call):
261
+
262
+ ```json
263
+ {
264
+ "rootEntityRef": "E2E_MULTI_202602221343",
265
+ "rootEntityType": "ORDER"
266
+ }
267
+ ```
268
+
269
+ Example (full data with all sections):
270
+
271
+ ```json
272
+ {
273
+ "rootEntityRef": "ORD-001",
274
+ "rootEntityType": "ORDER",
275
+ "compact": false,
276
+ "includeRuleDetails": true,
277
+ "includeCustomLogs": true,
278
+ "includeSnapshots": true,
279
+ "includeCrossEntity": true
280
+ }
281
+ ```
282
+
283
+ Example (lightweight):
284
+
285
+ ```json
286
+ {
287
+ "rootEntityRef": "ORD-001",
288
+ "rootEntityType": "ORDER",
289
+ "includeEventDetails": false,
290
+ "includeExceptions": false,
291
+ "includeNoMatchDetails": false,
292
+ "maxDrilldowns": 0
293
+ }
294
+ ```
295
+
296
+ ## `metrics.query`
297
+
298
+ Queries Prometheus metrics for Fluent runtime telemetry.
299
+
300
+ - **Required**: `query` (PromQL expression)
301
+ - **Optional**:
302
+ - `type`: `instant` (default) or `range`
303
+ - `time`: instant query timestamp
304
+ - `start`, `end`, `step`: required together for range queries
305
+ - `timeout`: query timeout in seconds (`1..120`)
306
+ - **Execution path**:
307
+ - instant -> GraphQL `metricInstant(query, time?)`
308
+ - range -> GraphQL `metricRange(query, start, end, step)`
309
+ - **Note**: `metrics.query` routes PromQL through Fluent's GraphQL proxy. Raw Prometheus REST endpoints are not relied on directly by this tool.
310
+
311
+ Label hygiene reminder:
312
+ - `core_event_received_total` does **not** include `status`
313
+ - `rubix_event_runtime_seconds_*` does include `status`
314
+ - histogram bucket series add `le`
315
+
316
+ Accurate counter delta pattern for wider windows:
317
+
318
+ ```promql
319
+ (last_over_time(metric[window]) - metric offset <period>)
320
+ or
321
+ last_over_time(metric[window])
322
+ ```
323
+
324
+ Use the fallback `or` arm when offset samples are missing.
325
+
326
+ Instant example:
327
+
328
+ ```json
329
+ {
330
+ "query": "sum(rate(rubix_event_runtime_seconds_count[5m]))",
331
+ "type": "instant"
332
+ }
333
+ ```
334
+
335
+ Range example:
336
+
337
+ ```json
338
+ {
339
+ "query": "rate(rubix_event_runtime_seconds_count{status=\"FAILED\"}[5m])",
340
+ "type": "range",
341
+ "start": "2026-02-20T00:00:00Z",
342
+ "end": "2026-02-20T01:00:00Z",
343
+ "step": "1m"
344
+ }
345
+ ```
346
+
347
+ ## `metrics.topEvents`
348
+
349
+ Returns ranked event analytics for a time window by aggregating Event API results.
350
+
351
+ - **Required**: `from`
352
+ - **Optional**:
353
+ - `to`: end timestamp (defaults to now)
354
+ - `entityType`: filter to one entity type
355
+ - `eventType`: defaults to `ORCHESTRATION`
356
+ - `topN`: ranked rows to return (`1..100`, default `20`)
357
+ - `maxPages`: Event API pagination cap (`1..50`, default `10`)
358
+ - **Output summary**:
359
+ - `totalEvents`, `failureRate`, `statusBreakdown`
360
+ - `topEvents[]` grouped by `name + entityType + status`
361
+ - `uniqueEventNames`, `uniqueEntityTypes`
362
+
363
+ Example:
364
+
365
+ ```json
366
+ {
367
+ "from": "2026-02-22T00:00:00Z",
368
+ "to": "2026-02-22T12:00:00Z",
369
+ "topN": 20,
370
+ "eventType": "ORCHESTRATION"
371
+ }
372
+ ```
373
+
374
+ ## `metrics.healthCheck`
375
+
376
+ Runs a compact, one-call anomaly assessment.
377
+
378
+ - **Optional**:
379
+ - `window`: Prometheus/Event API analysis window (`1h`, `6h`, `24h`, `7d`; default `1h`)
380
+ - `includeTopEvents`: include ranked event list in response (default `true`)
381
+ - `topN`: max top event rows (`1..100`, default `10`)
382
+ - `thresholds`: override defaults
383
+ - `failureRate` (default `5`)
384
+ - `pendingRate` (default `10`)
385
+ - `dominanceRate` (default `50`)
386
+ - **Behavior**:
387
+ - Prometheus-first (`metrics.query` equivalent calls under the hood)
388
+ - Falls back to Event API aggregation if Prometheus is unavailable
389
+
390
+ Example:
391
+
392
+ ```json
393
+ {
394
+ "window": "1h",
395
+ "includeTopEvents": true,
396
+ "topN": 10
397
+ }
398
+ ```
399
+
400
+ ## `metrics.sloReport`
401
+
402
+ Returns a managed-services SLO snapshot with explicit KPI fields and threshold findings.
403
+
404
+ - **Optional**:
405
+ - `window`: KPI window (`30m`, `1h`, `24h`, `7d`; default `1h`)
406
+ - `includeTopFailingEvents`: include top failed events from Event API (default `true`)
407
+ - `topN`: top failed rows (`1..100`, default `10`)
408
+ - `maxPages`: Event API pagination cap for fallback/failed ranking (`1..50`, default `10`)
409
+ - `thresholds`: override defaults
410
+ - `failureRate` (default `5`)
411
+ - `noMatchRate` (default `0`)
412
+ - `pendingRate` (default `10`)
413
+ - `runtimeP95Seconds` (default `5`)
414
+ - `inflightP95Seconds` (default `60`)
415
+ - **Output highlights**:
416
+ - total/failed/no-match/pending event counts
417
+ - failure/no-match/pending rates
418
+ - p95 runtime and inflight latency (null when Prometheus fallback path is used)
419
+ - findings + recommendations + source (`prometheus` or `event_api`)
420
+
421
+ Example:
422
+
423
+ ```json
424
+ {
425
+ "window": "1h",
426
+ "includeTopFailingEvents": true,
427
+ "topN": 10
428
+ }
429
+ ```
430
+
431
+ ## `metrics.labelCatalog`
432
+
433
+ Discovers what labels a metric supports so PromQL groupings are correct.
434
+
435
+ - **Required**: `metric`
436
+ - **Optional**:
437
+ - `window`: live series sampling window (`30m`, `1h`, `24h`, `7d`; default `24h`)
438
+ - `includeKnownLabels`: include Fluent-known label hints (default `true`)
439
+ - `maxValuesPerLabel`: max sample values returned per label (`1..50`, default `10`)
440
+ - **Behavior**:
441
+ - runs `last_over_time(<metric>[window])` as an instant query
442
+ - extracts label keys from returned vector series
443
+ - reports presence/cardinality/sample values per label
444
+ - merges known Fluent label hints when available
445
+
446
+ Example:
447
+
448
+ ```json
449
+ {
450
+ "metric": "core_event_received_total",
451
+ "window": "1h",
452
+ "maxValuesPerLabel": 10
453
+ }
454
+ ```
455
+
456
+ ## `workflow.transitions`
457
+
458
+ Query available user actions (transitions) for entities at a given workflow state.
459
+
460
+ Calls `POST /api/v4.1/transition` to discover what events can be fired, what attributes they require, and how they appear in the UI.
461
+
462
+ - **Required**: `triggers` (array), each trigger requires `retailerId`
463
+ - **Optional per trigger**: `type`, `subtype`, `status`, `module`, `flexType`, `flexVersion`, `name`
464
+ - **Retailer behavior**: `retailerId` is required per trigger. Falls back to `FLUENT_RETAILER_ID` when omitted.
465
+
466
+ ### Use cases
467
+
468
+ - Discover available actions at any workflow status without reading workflow JSON
469
+ - Build dynamic E2E test sequences that adapt to workflow changes
470
+ - Validate that expected user actions are available after deployment
471
+ - Get required event attributes for each action (avoids missing-attribute errors)
472
+
473
+ ### Integration with event.send
474
+
475
+ The `eventName` from each `userAction` maps directly to `event.send`'s `name` parameter. The `attributes[]` tell you what to include in `event.send`'s `attributes` parameter.
476
+
477
+ ### Example: ORDER at CREATED status
478
+
479
+ ```json
480
+ {
481
+ "triggers": [
482
+ {
483
+ "type": "ORDER",
484
+ "subtype": "HD",
485
+ "status": "CREATED",
486
+ "retailerId": "5",
487
+ "flexType": "ORDER::HD"
488
+ }
489
+ ]
490
+ }
491
+ ```
492
+
493
+ ### Example: MANIFEST with module filter
494
+
495
+ ```json
496
+ {
497
+ "triggers": [
498
+ {
499
+ "type": "MANIFEST",
500
+ "subtype": "DEFAULT",
501
+ "status": "PENDING",
502
+ "module": "servicepoint",
503
+ "flexType": "CARRIER::DEFAULT",
504
+ "retailerId": "2"
505
+ }
506
+ ]
507
+ }
508
+ ```
509
+
510
+ Typical success shape (truncated):
511
+
512
+ ```json
513
+ {
514
+ "ok": true,
515
+ "response": {
516
+ "response": [
517
+ {
518
+ "trigger": {
519
+ "type": "MANIFEST",
520
+ "subtype": "DEFAULT",
521
+ "status": "PENDING",
522
+ "module": "servicepoint",
523
+ "flexType": "CARRIER::DEFAULT",
524
+ "retailerId": "2"
525
+ },
526
+ "userActions": [
527
+ {
528
+ "eventName": "UPDATE",
529
+ "context": [
530
+ { "label": "SUBMIT", "type": "PRIMARY", "modules": ["servicepoint"], "confirm": false }
531
+ ],
532
+ "attributes": []
533
+ }
534
+ ]
535
+ }
536
+ ]
537
+ }
538
+ }
539
+ ```
540
+
541
+ ### Dynamic test sequence pattern
542
+
543
+ ```
544
+ 1. Create entity via graphql.query (mutation)
545
+ 2. workflow.transitions → get available actions at current status
546
+ 3. For each action:
547
+ a. event.send with eventName and required attributes from response
548
+ b. Poll entity status until transition completes
549
+ c. workflow.transitions → get next available actions
550
+ 4. Repeat until no more userActions (terminal state)
551
+ ```
552
+
553
+ ## `plugin.list`
554
+
555
+ List all registered orchestration rules (standard + custom) with metadata.
556
+
557
+ Calls `GET /orchestration/rest/v1/plugin` and returns a map keyed by fully-qualified rule name (e.g. `ACCOUNT.context.RuleName`).
558
+
559
+ - **Optional**: `name` (string) — case-insensitive substring filter on rule keys
560
+ - **Requires**: SDK client with `request` method
561
+
562
+ ### Each entry contains
563
+
564
+ - `ruleInfo`: name, description, accepted entity types, produced events
565
+ - `eventAttributes`: attributes the rule reads from events
566
+ - `parameters`: configurable rule parameters (with types and descriptions)
567
+
568
+ ### Use cases
569
+
570
+ - Understand what rules do when analyzing workflows
571
+ - Discover all registered rules (standard + custom) for a given account
572
+ - Find rules by name pattern (e.g. all "SendEvent" variants)
573
+ - Cross-reference deployed rules with local source code for module validation
574
+
575
+ ### Example: list all rules
576
+
577
+ ```json
578
+ {}
579
+ ```
580
+
581
+ ### Example: filter by name
582
+
583
+ ```json
584
+ {
585
+ "name": "SendEvent"
586
+ }
587
+ ```
588
+
589
+ Typical success shape (truncated):
590
+
591
+ ```json
592
+ {
593
+ "ok": true,
594
+ "response": {
595
+ "FLUENTRETAIL.base.SendEvent": {
596
+ "ruleInfo": {
597
+ "name": "SendEvent",
598
+ "description": "Sends an event to a specified entity",
599
+ "accepts": ["ORDER", "FULFILMENT", "ARTICLE"],
600
+ "produces": ["SendEvent"]
601
+ },
602
+ "eventAttributes": [
603
+ { "name": "eventName", "type": "STRING", "description": "Name of the event to send" }
604
+ ],
605
+ "parameters": [
606
+ { "name": "eventName", "type": "STRING", "description": "Event name to dispatch" }
607
+ ]
608
+ }
609
+ }
610
+ }
611
+ ```
612
+
613
+ ## `graphql.query`
614
+
615
+ Executes a Fluent Commerce GraphQL query or mutation through the SDK.
616
+
617
+ - **Required**: `query`
618
+ - **Optional**: `variables`
619
+ - **Retry behavior**:
620
+ - query operations use configured retry/backoff
621
+ - mutation operations disable retries automatically to avoid duplicate writes
622
+
623
+ ### Basic Query
624
+
625
+ ```json
626
+ {
627
+ "query": "query { orders(first: 5) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }"
628
+ }
629
+ ```
630
+
631
+ ### Pagination
632
+
633
+ Fluent uses Relay-style connections. Cursors live on each **edge**, not on `pageInfo`. There is no `endCursor` or `startCursor`.
634
+
635
+ ```json
636
+ {
637
+ "query": "query($cursor: String) { orders(first: 50, after: $cursor) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }",
638
+ "variables": { "cursor": "Y3Vyc29yOi0tLTM2X18xNzcxMTc2MzMyMTc0" }
639
+ }
640
+ ```
641
+
642
+ To paginate: take the `cursor` from the **last edge** in the response, pass it as the `after` variable. Repeat while `hasNextPage` is `true`.
643
+
644
+ Pagination args: `first`/`after` (forward), `last`/`before` (backward).
645
+
646
+ ### Mutation
647
+
648
+ ```json
649
+ {
650
+ "query": "mutation($input: UpdateOrderInput!) { updateOrder(input: $input) { id ref status } }",
651
+ "variables": { "input": { "id": "36", "status": "RECEIVED" } }
652
+ }
653
+ ```
654
+
655
+ ### Synchronous Fulfilment Options (Live Checkout)
656
+
657
+ `graphql.query` supports synchronous fulfilment options orchestration calls,
658
+ commonly used by checkout journeys to get plans in the same response.
659
+
660
+ ```json
661
+ {
662
+ "query": "mutation CreateFulfilmentOption($retailerId: Int!, $type: String!, $orderType: String!, $ref: String!, $products: [CreateFulfilmentOptionProductInput!], $longitude: Float!, $latitude: Float!, $radius: Json!) { createFulfilmentOption(input: { retailerId: $retailerId, type: $type, orderType: $orderType, ref: $ref, products: $products, address: { city: \"Kellyville Ridge\", state: \"New South Wales\", country: \"Australia\", postcode: \"2155\", addressLine1: \"Kellyville Ridge NSW, Australia\", latitude: $latitude, longitude: $longitude }, attributes: { name: \"radius\", type: \"DOUBLE\", value: $radius } }, executionMode: AWAIT_ORCHESTRATION) { id createdOn plans { edges { node { ref status eta splitCount fulfilments { locationRef eta items { productRef availableQuantity requestedQuantity } } } } } } }",
663
+ "variables": {
664
+ "retailerId": 5,
665
+ "type": "CC",
666
+ "orderType": "CC",
667
+ "ref": "CHECKOUT-FO-12345",
668
+ "products": [
669
+ { "productRef": "MP09-34-Blue", "requestedQuantity": 1, "catalogueRef": "" }
670
+ ],
671
+ "longitude": 150.9193671,
672
+ "latitude": -33.7068442,
673
+ "radius": 50
674
+ }
675
+ }
676
+ ```
677
+
678
+ Notes:
679
+ - `executionMode: AWAIT_ORCHESTRATION` makes the call synchronous.
680
+ - The environment must contain a matching workflow for `type` (for example `FULFILMENT_OPTIONS::CC`).
681
+ - If no matching workflow exists, Fluent returns a backend workflow-not-found error.
682
+
683
+ ### Connection Shape
684
+
685
+ All list queries return connections:
686
+
687
+ ```graphql
688
+ {
689
+ edges {
690
+ cursor
691
+ node { ...fields }
692
+ }
693
+ pageInfo { hasNextPage }
694
+ }
695
+ ```
696
+
697
+ Common query roots: `orders`, `fulfilments`, `fulfilmentChoices`, `locations`, `inventoryPositions`, `products`, `categories`, `settings`, `waves`, `articles`.
698
+
699
+ ## `batch.create`
700
+
701
+ Creates a Fluent ingestion job.
702
+
703
+ - **Required**: `name`
704
+ - **Optional**: `retailerId`, `entityType`, `action`
705
+ - **Note**: `retailerId` falls back to `FLUENT_RETAILER_ID` if omitted
706
+
707
+ Example:
708
+
709
+ ```json
710
+ {
711
+ "name": "inventory-load-2026-02-06",
712
+ "entityType": "INVENTORY_POSITION",
713
+ "action": "UPSERT"
714
+ }
715
+ ```
716
+
717
+ ## `batch.send`
718
+
719
+ Sends payload records to an existing batch job.
720
+
721
+ - **Required**:
722
+ - `jobId`
723
+ - `payload.action`
724
+ - `payload.entityType`
725
+ - `payload.entities` (non-empty array)
726
+
727
+ Example:
728
+
729
+ ```json
730
+ {
731
+ "jobId": "JOB-123",
732
+ "payload": {
733
+ "action": "UPSERT",
734
+ "entityType": "INVENTORY_POSITION",
735
+ "entities": [
736
+ {
737
+ "locationRef": "LOC-1",
738
+ "skuRef": "SKU-1",
739
+ "qty": 10,
740
+ "type": "LAST_ON_HAND",
741
+ "status": "ACTIVE"
742
+ }
743
+ ]
744
+ }
745
+ }
746
+ ```
747
+
748
+ ## `batch.status`
749
+
750
+ Reads status for a previously created batch job.
751
+
752
+ - **Required**: `jobId`
753
+
754
+ Example:
755
+
756
+ ```json
757
+ {
758
+ "jobId": "JOB-123"
759
+ }
760
+ ```
761
+
762
+ ## `batch.batchStatus`
763
+
764
+ Gets the status of a specific batch within a job. Useful for troubleshooting partial failures in multi-batch jobs.
765
+
766
+ - **Required**: `jobId`, `batchId`
767
+
768
+ Example:
769
+
770
+ ```json
771
+ {
772
+ "jobId": "JOB-123",
773
+ "batchId": "BATCH-456"
774
+ }
775
+ ```
776
+
777
+ ## `batch.results`
778
+
779
+ Gets per-record outcomes for a completed job. Call after `batch.status` reports a terminal state.
780
+
781
+ - **Required**: `jobId`
782
+
783
+ Example:
784
+
785
+ ```json
786
+ {
787
+ "jobId": "JOB-123"
788
+ }
789
+ ```
790
+
791
+ ---
792
+
793
+ ## `graphql.queryAll`
794
+
795
+ Executes a GraphQL query with SDK auto-pagination. Automatically follows cursors, merges all edges, and deduplicates by node ID. Use instead of `graphql.query` when you need ALL records from a connection.
796
+
797
+ - **Required**: `query`
798
+ - **Optional**:
799
+ - `variables`: query variables (include cursor variable, usually `null` for first page)
800
+ - `maxPages`: max pages to fetch (default: 100, max: 500)
801
+ - `maxRecords`: max total records to accumulate (default: 10000, max: 50000)
802
+ - `timeoutMs`: hard timeout for entire pagination run (default: 300000ms = 5 min)
803
+ - `direction`: `forward` (first/after) or `backward` (last/before)
804
+ - `errorHandling`: `throw` (fail on errors) or `partial` (return partial data + errors)
805
+ - **Timeout behavior**:
806
+ - uses `timeoutMs` for the whole pagination run
807
+ - independent from `FLUENT_REQUEST_TIMEOUT_MS` single-call timeout
808
+
809
+ ### How it works
810
+
811
+ 1. SDK detects pagination variables (`first`/`after` or `last`/`before`) in your query
812
+ 2. Executes the first page
813
+ 3. Extracts cursor from the last edge, follows `hasNextPage`
814
+ 4. Merges edges across pages, deduplicates by node ID
815
+ 5. Stops when: no more pages, maxPages reached, maxRecords reached, or timeout
816
+
817
+ ### Response
818
+
819
+ Includes `extensions.autoPagination`:
820
+
821
+ ```json
822
+ {
823
+ "totalPages": 5,
824
+ "totalRecords": 487,
825
+ "truncated": false,
826
+ "direction": "forward"
827
+ }
828
+ ```
829
+
830
+ If truncated: `truncationReason` will be `"maxPages"`, `"maxRecords"`, or `"timeout"`.
831
+
832
+ ### Example: Fetch all active orders
833
+
834
+ ```json
835
+ {
836
+ "query": "query($cursor: String) { orders(first: 100, after: $cursor, status: \"ACTIVE\") { edges { cursor node { id ref status createdOn } } pageInfo { hasNextPage } } }",
837
+ "variables": { "cursor": null },
838
+ "maxRecords": 5000
839
+ }
840
+ ```
841
+
842
+ ### Example: Fetch all locations (backward)
843
+
844
+ ```json
845
+ {
846
+ "query": "query($cursor: String) { locations(last: 50, before: $cursor) { edges { cursor node { id ref name type } } pageInfo { hasPreviousPage } } }",
847
+ "variables": { "cursor": null },
848
+ "direction": "backward"
849
+ }
850
+ ```
851
+
852
+ ## `graphql.batchMutate`
853
+
854
+ Executes multiple GraphQL mutations in a single request using aliased mutations. Sends up to 50 mutations at once, each with its own input.
855
+
856
+ - **Required**: `mutation`, `inputs`
857
+ - **Optional**: `returnFields`, `operationName`
858
+ - **Retry behavior**: disabled (non-idempotent write operation)
859
+
860
+ ### How it works
861
+
862
+ 1. Builds an aliased mutation query:
863
+ ```graphql
864
+ mutation BatchUpdateOrders($input1: UpdateOrderInput!, $input2: UpdateOrderInput!) {
865
+ updateOrder1: updateOrder(input: $input1) { id ref status }
866
+ updateOrder2: updateOrder(input: $input2) { id ref status }
867
+ }
868
+ ```
869
+ 2. Sends as a single GraphQL request
870
+ 3. Parses per-mutation results (success/failure for each input)
871
+
872
+ ### Response
873
+
874
+ ```json
875
+ {
876
+ "ok": true,
877
+ "summary": "All 3 mutations succeeded",
878
+ "executed": 3,
879
+ "failed": 0,
880
+ "allSucceeded": true,
881
+ "allFailed": false,
882
+ "results": [
883
+ { "alias": "updateOrder1", "index": 0, "success": true, "data": { "id": "1", "ref": "ORD-001", "status": "SHIPPED" } },
884
+ { "alias": "updateOrder2", "index": 1, "success": true, "data": { "id": "2", "ref": "ORD-002", "status": "SHIPPED" } }
885
+ ]
886
+ }
887
+ ```
888
+
889
+ On partial failure, `errors` array includes per-mutation error details with `alias`, `index`, `message`, and `inputRef`.
890
+
891
+ ### Example: Bulk update order statuses
892
+
893
+ ```json
894
+ {
895
+ "mutation": "updateOrder",
896
+ "inputs": [
897
+ { "id": "36", "status": "SHIPPED" },
898
+ { "id": "37", "status": "SHIPPED" },
899
+ { "id": "38", "status": "SHIPPED" }
900
+ ],
901
+ "returnFields": ["id", "ref", "status"]
902
+ }
903
+ ```
904
+
905
+ ### Example: Batch create inventory positions
906
+
907
+ ```json
908
+ {
909
+ "mutation": "updateInventoryPosition",
910
+ "inputs": [
911
+ { "id": "100", "status": "ACTIVE" },
912
+ { "id": "101", "status": "ACTIVE" }
913
+ ],
914
+ "returnFields": ["id", "ref", "status", "onHand"]
915
+ }
916
+ ```
917
+
918
+ ## `graphql.introspect`
919
+
920
+ Inspects the Fluent Commerce GraphQL schema via introspection. Fetches the full schema and caches it for 1 hour. Use to discover mutations, input types, and field requirements at runtime.
921
+
922
+ - **Optional** (specify one mode):
923
+ - `type`: name of an INPUT_OBJECT type to inspect (e.g., `UpdateOrderInput`)
924
+ - `mutation`: name of a mutation to inspect (e.g., `updateOrder`)
925
+ - `listMutations`: `true` to list all available mutation names
926
+ - `listQueries`: `true` to list all available query root field names
927
+
928
+ ### Example: List all mutations
929
+
930
+ ```json
931
+ {
932
+ "listMutations": true
933
+ }
934
+ ```
935
+
936
+ Response:
937
+
938
+ ```json
939
+ {
940
+ "ok": true,
941
+ "mutations": ["createOrder", "updateOrder", "createFulfilment", "..."],
942
+ "count": 142
943
+ }
944
+ ```
945
+
946
+ ### Example: Inspect a mutation
947
+
948
+ ```json
949
+ {
950
+ "mutation": "updateOrder"
951
+ }
952
+ ```
953
+
954
+ Response:
955
+
956
+ ```json
957
+ {
958
+ "ok": true,
959
+ "mutation": {
960
+ "name": "updateOrder",
961
+ "description": "Updates an existing order",
962
+ "args": [
963
+ {
964
+ "name": "input",
965
+ "type": "UpdateOrderInput!",
966
+ "required": true
967
+ }
968
+ ],
969
+ "returnType": "Order"
970
+ }
971
+ }
972
+ ```
973
+
974
+ ### Example: Inspect an input type
975
+
976
+ ```json
977
+ {
978
+ "type": "UpdateOrderInput"
979
+ }
980
+ ```
981
+
982
+ Response:
983
+
984
+ ```json
985
+ {
986
+ "ok": true,
987
+ "inputType": {
988
+ "name": "UpdateOrderInput",
989
+ "fields": [
990
+ { "name": "id", "type": "ID!", "required": true },
991
+ { "name": "ref", "type": "String", "required": false },
992
+ { "name": "status", "type": "String", "required": false },
993
+ { "name": "attributes", "type": "[AttributeInput]", "required": false, "isArray": true }
994
+ ]
995
+ }
996
+ }
997
+ ```
998
+
999
+ ### Chaining example
1000
+
1001
+ 1. `{ "listMutations": true }` → find `updateOrder`
1002
+ 2. `{ "mutation": "updateOrder" }` → see it takes `UpdateOrderInput!`
1003
+ 3. `{ "type": "UpdateOrderInput" }` → see all updatable fields
1004
+ 4. Use `graphql.query` or `graphql.batchMutate` with the discovered schema
1005
+
1006
+ ## `connection.test`
1007
+
1008
+ Comprehensive Fluent Commerce connectivity test. Authenticates, executes a `me` query, and returns the authenticated user's details.
1009
+
1010
+ - **Input**: no fields
1011
+
1012
+ ### Response (success)
1013
+
1014
+ ```json
1015
+ {
1016
+ "ok": true,
1017
+ "duration": 342,
1018
+ "details": {
1019
+ "userId": "12",
1020
+ "username": "admin@hmtest.com",
1021
+ "userType": "RETAILER",
1022
+ "userStatus": "ACTIVE",
1023
+ "retailerId": "5",
1024
+ "retailerRef": "HMTEST",
1025
+ "retailerName": "HM Test",
1026
+ "locationId": "10",
1027
+ "locationRef": "W252",
1028
+ "locationName": "Warehouse 252"
1029
+ }
1030
+ }
1031
+ ```
1032
+
1033
+ ### Response (failure)
1034
+
1035
+ ```json
1036
+ {
1037
+ "ok": false,
1038
+ "duration": 1500,
1039
+ "error": "Authentication failed after 3 retries: 401 Unauthorized"
1040
+ }
1041
+ ```
1042
+
1043
+ More thorough than `health.ping` — actually verifies the GraphQL endpoint works end-to-end. Use when first connecting, debugging auth issues, or verifying retailer/location context.
1044
+
1045
+ ## `webhook.validate`
1046
+
1047
+ Validates a Fluent Commerce webhook payload and optionally verifies its signature.
1048
+
1049
+ - **Required**: `payload`
1050
+ - **Optional**:
1051
+ - `rawBody`: exact original HTTP request body string for signature checks
1052
+ - `signature`: the `X-Fluent-Signature` header value (base64-encoded)
1053
+ - `publicKey`: the Fluent public key (PEM format) for signature verification
1054
+ - `algorithm`: `SHA512withRSA` (default) or `MD5withRSA`
1055
+
1056
+ ### Mode 1: Basic validation
1057
+
1058
+ Checks that required fields (`name`, `id`, `retailerId`) are present in the payload.
1059
+
1060
+ ```json
1061
+ {
1062
+ "payload": {
1063
+ "name": "OrderCreated",
1064
+ "id": "event-123",
1065
+ "retailerId": "5",
1066
+ "entityType": "ORDER",
1067
+ "entityRef": "ORD-001"
1068
+ }
1069
+ }
1070
+ ```
1071
+
1072
+ Response:
1073
+
1074
+ ```json
1075
+ {
1076
+ "ok": true,
1077
+ "basicValidation": { "valid": true, "missingFields": [] },
1078
+ "note": "Basic field validation passed. Provide signature + publicKey for signature verification."
1079
+ }
1080
+ ```
1081
+
1082
+ ### Mode 2: Signature validation
1083
+
1084
+ Verifies the webhook body against the `X-Fluent-Signature` header using the provided public key.
1085
+ Use `rawBody` whenever possible because signature verification is byte-sensitive.
1086
+
1087
+ ```json
1088
+ {
1089
+ "payload": {
1090
+ "name": "OrderCreated",
1091
+ "id": "event-123",
1092
+ "retailerId": "5"
1093
+ },
1094
+ "rawBody": "{\"name\":\"OrderCreated\",\"id\":\"event-123\",\"retailerId\":\"5\"}",
1095
+ "signature": "base64-encoded-signature-from-header",
1096
+ "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----",
1097
+ "algorithm": "SHA512withRSA"
1098
+ }
1099
+ ```
1100
+
1101
+ Response:
1102
+
1103
+ ```json
1104
+ {
1105
+ "ok": true,
1106
+ "basicValidation": { "valid": true, "missingFields": [] },
1107
+ "signatureValidation": {
1108
+ "valid": true,
1109
+ "algorithm": "SHA512withRSA",
1110
+ "payloadSource": "rawBody"
1111
+ }
1112
+ }
1113
+ ```
1114
+
1115
+ ---
1116
+
1117
+ ## `entity.create`
1118
+
1119
+ Type-safe entity creation with built-in validation and gotcha knowledge.
1120
+
1121
+ **Supported entity types:** ORDER, FULFILMENT, LOCATION, NETWORK, CUSTOMER, PRODUCT, INVENTORY_POSITION, VIRTUAL_CATALOGUE, VIRTUAL_POSITION, CATEGORY, CARRIER, SETTING
1122
+
1123
+ - **Required**:
1124
+ - `entityType`: entity type string (see supported list above)
1125
+ - `data`: creation input fields matching the GraphQL create input type
1126
+ - **Optional**:
1127
+ - `returnFields`: array of field names to return (defaults to entity type defaults)
1128
+ - `dryRun`: if `true`, builds and validates the mutation without executing (default `false`)
1129
+
1130
+ **Key behaviors:**
1131
+
1132
+ - Validates required fields BEFORE sending (e.g., Location requires `openingSchedule`)
1133
+ - Encodes compound key rules (ProductKey needs `ref` + `catalogue.ref`)
1134
+ - Auto-resolves `retailerId` from config for retailer-scoped entities
1135
+ - Returns the executed mutation string for audit trail
1136
+
1137
+ **Known gotchas (encoded in validation):**
1138
+
1139
+ - No create inputs have a `status` field — status is auto-set to `CREATED`
1140
+ - `Customer` has NO `ref` field; `username` is the identifier
1141
+ - `Network` uses `name` (not `ref`) in create; `retailers` is a plural array
1142
+ - `Location` requires `openingSchedule` even for 24/7 (use `allHours: true`)
1143
+ - `Product` `gtin` has a 20-character max
1144
+ - `Setting` `context` is a plain String, `contextId` is a separate Int
1145
+
1146
+ **Example** — create a location (dry run):
1147
+
1148
+ ```json
1149
+ {
1150
+ "entityType": "LOCATION",
1151
+ "data": {
1152
+ "ref": "LOC_WH_01",
1153
+ "type": "WAREHOUSE",
1154
+ "name": "Main Warehouse",
1155
+ "openingSchedule": { "allHours": true }
1156
+ },
1157
+ "dryRun": true
1158
+ }
1159
+ ```
1160
+
1161
+ Response (dry run):
1162
+
1163
+ ```json
1164
+ {
1165
+ "ok": true,
1166
+ "dryRun": true,
1167
+ "entityType": "LOCATION",
1168
+ "mutation": "mutation CreateLocation($input: CreateLocationInput!) { createLocation(input: $input) { id ref status type name } }",
1169
+ "inputType": "CreateLocationInput",
1170
+ "variables": { "input": { "ref": "LOC_WH_01", "type": "WAREHOUSE", "name": "Main Warehouse", "openingSchedule": { "allHours": true } } },
1171
+ "requiredFields": ["ref", "type", "name", "openingSchedule"],
1172
+ "gotchas": ["Location requires openingSchedule — use { allHours: true } for 24/7"],
1173
+ "note": "No API call made. Set dryRun=false to execute."
1174
+ }
1175
+ ```
1176
+
1177
+ **Example** — create a location (real):
1178
+
1179
+ ```json
1180
+ {
1181
+ "entityType": "LOCATION",
1182
+ "data": {
1183
+ "ref": "LOC_WH_01",
1184
+ "type": "WAREHOUSE",
1185
+ "name": "Main Warehouse",
1186
+ "openingSchedule": { "allHours": true }
1187
+ }
1188
+ }
1189
+ ```
1190
+
1191
+ Response:
1192
+
1193
+ ```json
1194
+ {
1195
+ "ok": true,
1196
+ "entityType": "LOCATION",
1197
+ "entity": { "id": "42", "ref": "LOC_WH_01", "status": "CREATED", "type": "WAREHOUSE", "name": "Main Warehouse" },
1198
+ "mutation": "mutation CreateLocation($input: CreateLocationInput!) { ... }"
1199
+ }
1200
+ ```
1201
+
1202
+ ---
1203
+
1204
+ ## `entity.update`
1205
+
1206
+ Status-aware entity updates with optional transition validation.
1207
+
1208
+ - **Required**:
1209
+ - `entityType`: entity type string
1210
+ - `id`: entity ID (required for updates)
1211
+ - `fields`: object of fields to update (matches GraphQL update input type)
1212
+ - **Optional**:
1213
+ - `returnFields`: array of field names to return
1214
+ - `validateTransition`: if `true` and `status` is being changed, queries `workflow.transitions` to verify the transition is allowed before executing (default `false`)
1215
+
1216
+ **Key behaviors:**
1217
+
1218
+ - Auto-detects the correct GraphQL mutation name from entity type
1219
+ - When `validateTransition` is `true`: fetches current entity state, queries transition API, warns if no workflow transitions exist from the current status
1220
+ - Returns the previous status for audit trail when transition validation is used
1221
+ - Uses no-retry semantics (write operation)
1222
+
1223
+ **Example** — update order status with transition validation:
1224
+
1225
+ ```json
1226
+ {
1227
+ "entityType": "ORDER",
1228
+ "id": "12345",
1229
+ "fields": { "status": "COMPLETE" },
1230
+ "validateTransition": true
1231
+ }
1232
+ ```
1233
+
1234
+ Response:
1235
+
1236
+ ```json
1237
+ {
1238
+ "ok": true,
1239
+ "entityType": "ORDER",
1240
+ "entity": { "id": "12345", "ref": "HD-001", "status": "COMPLETE" },
1241
+ "mutation": "mutation UpdateOrder($input: UpdateOrderInput!) { ... }"
1242
+ }
1243
+ ```
1244
+
1245
+ If no workflow transition exists:
1246
+
1247
+ ```json
1248
+ {
1249
+ "ok": true,
1250
+ "entityType": "ORDER",
1251
+ "entity": { "id": "12345", "ref": "HD-001", "status": "COMPLETE" },
1252
+ "mutation": "...",
1253
+ "transitionWarning": "No workflow transitions available from status \"BOOKED\" for ORDER. The status update may succeed as a direct mutation but will not trigger workflow orchestration."
1254
+ }
1255
+ ```
1256
+
1257
+ ---
1258
+
1259
+ ## `entity.get`
1260
+
1261
+ Unified entity lookup by ID or ref with optional edge inclusion.
1262
+
1263
+ - **Required**:
1264
+ - `entityType`: entity type string
1265
+ - At least one of `id` or `ref`
1266
+ - **Optional**:
1267
+ - `id`: entity ID (preferred lookup method)
1268
+ - `ref`: entity ref (fallback — not available for CUSTOMER)
1269
+ - `fields`: array of field names to return (defaults to entity type defaults: id, ref, status, type, createdOn, etc.)
1270
+ - `includeEdges`: array of related entity edges to fetch (e.g., `["fulfilments", "items", "attributes"]`)
1271
+
1272
+ **Key behaviors:**
1273
+
1274
+ - Prefers ID-based lookup (single entity query) over ref-based lookup (connection query)
1275
+ - Ref-based lookup uses the connection query pattern (edges/node)
1276
+ - Returns `{ found: false }` when entity is not found (not an error)
1277
+ - CUSTOMER entity type does not support ref-based lookup
1278
+
1279
+ **Example** — get order by ref with fulfilments:
1280
+
1281
+ ```json
1282
+ {
1283
+ "entityType": "ORDER",
1284
+ "ref": "HD-001",
1285
+ "includeEdges": ["fulfilments"]
1286
+ }
1287
+ ```
1288
+
1289
+ Response:
1290
+
1291
+ ```json
1292
+ {
1293
+ "ok": true,
1294
+ "found": true,
1295
+ "entityType": "ORDER",
1296
+ "entity": {
1297
+ "id": "12345",
1298
+ "ref": "HD-001",
1299
+ "status": "BOOKED",
1300
+ "type": "HD",
1301
+ "fulfilments": {
1302
+ "edges": [
1303
+ { "node": { "id": "67890", "ref": "FUL-001", "status": "CREATED" } }
1304
+ ]
1305
+ }
1306
+ }
1307
+ }
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ ## `workflow.upload`
1313
+
1314
+ Deploy a workflow JSON definition to the Fluent environment.
1315
+
1316
+ Uploads via REST API `POST /api/v4.1/workflow/{retailerId}` — the same endpoint the Fluent CLI uses.
1317
+
1318
+ - **Required**:
1319
+ - `workflow`: workflow JSON definition (as object or JSON string)
1320
+ - **Optional**:
1321
+ - `retailerId`: target retailer ID (falls back to `FLUENT_RETAILER_ID`)
1322
+ - `validate`: validate structure before uploading (default `true`)
1323
+ - `dryRun`: validate only, do not deploy (default `false`)
1324
+
1325
+ **Validation checks:**
1326
+
1327
+ - `name` field present
1328
+ - At least one status defined in `statuses` array
1329
+ - All rulesets have `name`, `triggers`, and `rules` (warns if empty)
1330
+
1331
+ **Important:** For production deployments, prefer `fluent module install` via CLI which bundles workflows with settings, rules, and data in a versioned module. Use this tool for interactive editing, hotfixes, or when CLI is unavailable.
1332
+
1333
+ **Example** — dry-run validation:
1334
+
1335
+ ```json
1336
+ {
1337
+ "workflow": {
1338
+ "name": "ORDER::HD",
1339
+ "type": "ORDER",
1340
+ "subtype": "HD",
1341
+ "statuses": [{ "name": "CREATED" }, { "name": "BOOKED" }],
1342
+ "rulesets": [
1343
+ {
1344
+ "name": "BookOrder",
1345
+ "triggers": [{ "status": "CREATED" }],
1346
+ "rules": [{ "name": "com.fluentretail.rubix.rule.order.SendEventOnVerifyingState", "props": { "status": "BOOKED" } }]
1347
+ }
1348
+ ]
1349
+ },
1350
+ "dryRun": true
1351
+ }
1352
+ ```
1353
+
1354
+ Response (dry run — valid):
1355
+
1356
+ ```json
1357
+ {
1358
+ "ok": true,
1359
+ "dryRun": true,
1360
+ "valid": true,
1361
+ "workflowName": "ORDER::HD",
1362
+ "retailerId": "5",
1363
+ "note": "Validation passed. Set dryRun=false to deploy."
1364
+ }
1365
+ ```
1366
+
1367
+ ---
1368
+
1369
+ ## `workflow.diff`
1370
+
1371
+ Compare two workflow JSON definitions and identify changes.
1372
+
1373
+ Pure local computation — no API calls.
1374
+
1375
+ - **Required**:
1376
+ - `base`: base workflow JSON (before changes)
1377
+ - `target`: target workflow JSON (after changes)
1378
+ - **Optional**:
1379
+ - `format`: output format — `summary` (default), `detailed`, or `mermaid`
1380
+
1381
+ **Comparison scope:**
1382
+
1383
+ - Rulesets: added, removed, modified (trigger changes, rule additions/removals, prop changes)
1384
+ - Statuses: added, removed
1385
+ - Risk assessment: removing rulesets or statuses = HIGH, modifying props = MEDIUM, adding = LOW
1386
+
1387
+ **Formats:**
1388
+
1389
+ | Format | Returns |
1390
+ |---|---|
1391
+ | `summary` | Change counts, status changes, risk level |
1392
+ | `detailed` | Full `WorkflowDiffResult` object with per-ruleset breakdown |
1393
+ | `mermaid` | `stateDiagram-v2` with color-coded added/removed/modified states and transitions |
1394
+
1395
+ **Example** — summary diff:
1396
+
1397
+ ```json
1398
+ {
1399
+ "base": { "name": "ORDER::HD", "statuses": [...], "rulesets": [...] },
1400
+ "target": { "name": "ORDER::HD", "statuses": [...], "rulesets": [...] },
1401
+ "format": "summary"
1402
+ }
1403
+ ```
1404
+
1405
+ Response:
1406
+
1407
+ ```json
1408
+ {
1409
+ "ok": true,
1410
+ "summary": "3 ruleset change(s): 1 added, 0 removed, 2 modified. 1 status change(s): 1 added, 0 removed. Risk level: MEDIUM.",
1411
+ "riskLevel": "medium",
1412
+ "rulesetChanges": { "added": 1, "removed": 0, "modified": 2 },
1413
+ "statusChanges": { "added": ["AWAITING_PICKUP"], "removed": [] }
1414
+ }
1415
+ ```
1416
+
1417
+ ---
1418
+
1419
+ ## `workflow.simulate`
1420
+
1421
+ Static prediction of workflow event outcomes from workflow JSON.
1422
+
1423
+ Pure local computation — no API calls. Parses workflow JSON to find matching rulesets and predict state transitions, follow-on events, and webhook side effects.
1424
+
1425
+ - **Required**:
1426
+ - `workflow`: workflow JSON definition (as object or JSON string)
1427
+ - `currentStatus`: entity status to simulate from (e.g., `CREATED`, `BOOKED`)
1428
+ - **Optional**:
1429
+ - `eventName`: event name to match against ruleset names (if omitted, returns all rulesets triggered by `currentStatus`)
1430
+ - `entityType`: entity type filter for trigger matching
1431
+ - `entitySubtype`: entity subtype filter for trigger matching
1432
+
1433
+ **What it does:**
1434
+
1435
+ - Finds rulesets whose triggers match `currentStatus` (and optionally `eventName`)
1436
+ - Extracts `SetState`/`ChangeState` rules → predicted next status
1437
+ - Extracts `SendEvent` rules → predicted follow-on events
1438
+ - Extracts `SendWebhook` rules → predicted webhook side effects
1439
+ - Identifies custom Java rules that cannot be statically predicted
1440
+
1441
+ **Important limitations (always disclosed in response):**
1442
+
1443
+ - **STATIC ONLY** — cannot evaluate runtime conditions, entity attributes, or settings values
1444
+ - **CUSTOM RULES OPAQUE** — Java plugin rules may conditionally execute; behavior unknown without live state
1445
+ - **NO CROSS-ENTITY** — does not follow SendEvent chains into child entity workflows
1446
+ - Use `workflow.transitions` for **authoritative live validation** of available actions
1447
+
1448
+ **Example** — simulate from CREATED status:
1449
+
1450
+ ```json
1451
+ {
1452
+ "workflow": { "name": "ORDER::HD", "rulesets": [...] },
1453
+ "currentStatus": "CREATED",
1454
+ "eventName": "BookOrder"
1455
+ }
1456
+ ```
1457
+
1458
+ Response:
1459
+
1460
+ ```json
1461
+ {
1462
+ "ok": true,
1463
+ "workflowName": "ORDER::HD",
1464
+ "currentStatus": "CREATED",
1465
+ "eventName": "BookOrder",
1466
+ "matchedRulesets": 1,
1467
+ "rulesets": [
1468
+ {
1469
+ "name": "BookOrder",
1470
+ "matchType": "statusAndEvent",
1471
+ "predictedStatus": "BOOKED",
1472
+ "predictedEvents": ["SendToFulfilment"],
1473
+ "predictedWebhooks": [],
1474
+ "ruleCount": 3
1475
+ }
1476
+ ],
1477
+ "prediction": {
1478
+ "likelyNextStatus": "BOOKED",
1479
+ "followOnEvents": ["SendToFulfilment"],
1480
+ "webhookSideEffects": [],
1481
+ "confidence": "low",
1482
+ "note": "Custom rules present — prediction may be incomplete or wrong."
1483
+ },
1484
+ "limitations": [
1485
+ "STATIC PREDICTION ONLY — does not account for runtime conditions, entity attributes, or settings values.",
1486
+ "1 custom rule(s) found whose behavior cannot be predicted statically: ForwardIfOrderCoordinatesPresent.",
1487
+ "Use workflow.transitions for AUTHORITATIVE live validation of available actions."
1488
+ ]
1489
+ }
1490
+ ```
1491
+
1492
+ ---
1493
+
1494
+ ## `setting.upsert`
1495
+
1496
+ Create or update a Fluent Commerce setting with upsert semantics.
1497
+
1498
+ Queries existing settings by `name` + `context` + `contextId` first. Creates if missing, updates if exists.
1499
+
1500
+ - **Required**:
1501
+ - `name`: setting key/name
1502
+ - `value`: setting value (for small values)
1503
+ - `context`: setting scope — `RETAILER`, `ACCOUNT`, `LOCATION`, `NETWORK`, `AGENT`, `CUSTOMER`
1504
+ - **Optional**:
1505
+ - `lobValue`: large object value for JSON payloads > 4KB (mutually exclusive with `value`)
1506
+ - `contextId`: context ID (e.g., retailer ID). Defaults to `FLUENT_RETAILER_ID` for `RETAILER` context.
1507
+
1508
+ **Key gotchas:**
1509
+
1510
+ - `context` is a plain String (`"RETAILER"`), NOT an object
1511
+ - `contextId` is a separate Int field
1512
+ - For large JSON values (> 4KB), use `lobValue` instead of `value`
1513
+ - Returns `created: true/false` for audit trail
1514
+
1515
+ **Example** — create a webhook URL setting:
1516
+
1517
+ ```json
1518
+ {
1519
+ "name": "WEBHOOK_ORDER_NOTIFICATION",
1520
+ "value": "https://api.example.com/webhooks/orders",
1521
+ "context": "RETAILER"
1522
+ }
1523
+ ```
1524
+
1525
+ Response (created):
1526
+
1527
+ ```json
1528
+ {
1529
+ "ok": true,
1530
+ "created": true,
1531
+ "updated": false,
1532
+ "setting": {
1533
+ "id": "99",
1534
+ "name": "WEBHOOK_ORDER_NOTIFICATION",
1535
+ "value": "https://api.example.com/webhooks/orders",
1536
+ "context": "RETAILER",
1537
+ "contextId": 5
1538
+ }
1539
+ }
1540
+ ```
1541
+
1542
+ Response (updated existing):
1543
+
1544
+ ```json
1545
+ {
1546
+ "ok": true,
1547
+ "created": false,
1548
+ "updated": true,
1549
+ "setting": { "id": "99", "name": "WEBHOOK_ORDER_NOTIFICATION", "value": "https://api.example.com/webhooks/orders-v2", "context": "RETAILER", "contextId": 5 },
1550
+ "previousValue": "https://api.example.com/webhooks/orders"
1551
+ }
1552
+ ```
1553
+
1554
+ ---
1555
+
1556
+ ## `setting.bulkUpsert`
1557
+
1558
+ Batch create or update multiple settings in one call.
1559
+
1560
+ Processes up to 50 settings sequentially with individual error handling. Each setting is an independent upsert — failures on one setting don't block others.
1561
+
1562
+ - **Required**:
1563
+ - `settings`: array of setting objects (min 1, max 50), each with `name`, `value`, `context`, and optional `lobValue` and `contextId`
1564
+
1565
+ **Example** — bulk create 3 settings:
1566
+
1567
+ ```json
1568
+ {
1569
+ "settings": [
1570
+ { "name": "WEBHOOK_ORDER_URL", "value": "https://api.example.com/orders", "context": "RETAILER" },
1571
+ { "name": "WEBHOOK_FULFILMENT_URL", "value": "https://api.example.com/fulfilments", "context": "RETAILER" },
1572
+ { "name": "FEATURE_FLAG_RETURNS", "value": "true", "context": "RETAILER" }
1573
+ ]
1574
+ }
1575
+ ```
1576
+
1577
+ Response:
1578
+
1579
+ ```json
1580
+ {
1581
+ "ok": true,
1582
+ "created": 2,
1583
+ "updated": 1,
1584
+ "failed": 0,
1585
+ "total": 3,
1586
+ "results": [
1587
+ { "name": "WEBHOOK_ORDER_URL", "status": "created" },
1588
+ { "name": "WEBHOOK_FULFILMENT_URL", "status": "created" },
1589
+ { "name": "FEATURE_FLAG_RETURNS", "status": "updated" }
1590
+ ]
1591
+ }
1592
+ ```
1593
+
1594
+ ---
1595
+
1596
+ ## `environment.discover`
1597
+
1598
+ Full environment snapshot in one call.
1599
+
1600
+ Returns everything an agent needs to understand the Fluent environment: retailer details, locations, networks, catalogues, workflows, settings, modules, and user context.
1601
+
1602
+ - **Optional**:
1603
+ - `include`: array of sections to include. Default: `["retailer", "locations", "networks", "catalogues"]`. Available: `retailer`, `locations`, `networks`, `catalogues`, `workflows`, `settings`, `modules`, `users`
1604
+
1605
+ **Limitations:**
1606
+
1607
+ - Locations and settings return the first 100 items only (no auto-pagination)
1608
+ - Workflows section uses a transition API probe — may miss workflows with no user actions at the initial state. Use `fluent workflow list` via CLI for definitive listing.
1609
+
1610
+ **Example** — discover retailer, locations, and settings:
1611
+
1612
+ ```json
1613
+ {
1614
+ "include": ["retailer", "locations", "settings"]
1615
+ }
1616
+ ```
1617
+
1618
+ Response:
1619
+
1620
+ ```json
1621
+ {
1622
+ "ok": true,
1623
+ "retailer": { "id": "5", "ref": "HM_TEST", "tradingName": "HM Test", "status": "ACTIVE" },
1624
+ "locations": [
1625
+ { "id": "10", "ref": "LOC_WH_01", "type": "WAREHOUSE", "status": "ACTIVE", "name": "Main Warehouse" }
1626
+ ],
1627
+ "settings": [
1628
+ { "name": "WEBHOOK_ORDER_URL", "value": "https://...", "context": "RETAILER", "contextId": 5 }
1629
+ ]
1630
+ }
1631
+ ```
1632
+
1633
+ ---
1634
+
1635
+ ## `environment.validate`
1636
+
1637
+ Pre-flight environment validation. Runs configurable checks before E2E tests or deployments.
1638
+
1639
+ - **Optional**:
1640
+ - `checks`: array of checks to run. Default: `["auth", "retailer", "locations"]`. Available: `auth`, `retailer`, `locations`, `inventory`, `workflows`, `settings`, `modules`
1641
+
1642
+ **Available checks:**
1643
+
1644
+ | Check | What it validates |
1645
+ |---|---|
1646
+ | `auth` | Token valid, permissions sufficient |
1647
+ | `retailer` | Retailer exists and is active |
1648
+ | `locations` | At least one warehouse location exists |
1649
+ | `inventory` | At least one product with stock |
1650
+ | `workflows` | Key workflows deployed (ORDER, FULFILMENT) |
1651
+ | `settings` | Critical settings exist |
1652
+ | `modules` | Expected modules deployed |
1653
+
1654
+ Returns pass/fail per check with severity and actionable messages.
1655
+
1656
+ **Example** — full pre-flight validation:
1657
+
1658
+ ```json
1659
+ {
1660
+ "checks": ["auth", "retailer", "locations", "workflows", "settings"]
1661
+ }
1662
+ ```
1663
+
1664
+ Response:
1665
+
1666
+ ```json
1667
+ {
1668
+ "ok": true,
1669
+ "results": [
1670
+ { "check": "auth", "passed": true, "severity": "critical", "message": "Authenticated as admin@example.com" },
1671
+ { "check": "retailer", "passed": true, "severity": "critical", "message": "Retailer HM_TEST (ID 5) is ACTIVE" },
1672
+ { "check": "locations", "passed": true, "severity": "high", "message": "Found 3 locations (2 warehouses)" },
1673
+ { "check": "workflows", "passed": false, "severity": "high", "message": "Missing workflow: FULFILMENT_OPTIONS::HD" },
1674
+ { "check": "settings", "passed": true, "severity": "medium", "message": "12 retailer settings found" }
1675
+ ],
1676
+ "summary": { "passed": 4, "failed": 1, "total": 5 }
1677
+ }
1678
+ ```
1679
+
1680
+ ---
1681
+
1682
+ ## `test.assert`
1683
+
1684
+ Assert entity state matches expectations with optional polling.
1685
+
1686
+ Deep assertion on entity fields, attributes, and edge counts/statuses with human-readable failure messages.
1687
+
1688
+ - **Required**:
1689
+ - `entityType`: entity type (ORDER, FULFILMENT, etc.)
1690
+ - `assertions`: object with expected values
1691
+ - **Optional**:
1692
+ - `id`: entity ID (preferred)
1693
+ - `ref`: entity ref (fallback)
1694
+ - `poll`: if `true`, retry until assertions pass or timeout (default `false`)
1695
+ - `timeoutMs`: polling timeout in ms, 1000–300000 (default `60000`)
1696
+ - `intervalMs`: polling interval in ms, 1000–30000 (default `5000`)
1697
+
1698
+ **Assertion types:**
1699
+
1700
+ | Assertion | Description |
1701
+ |---|---|
1702
+ | `status` | Exact match on entity status |
1703
+ | `type` | Exact match on entity type |
1704
+ | `subtype` | Exact match on entity subtype |
1705
+ | `attributes` | Key-value pairs that must be present on the entity |
1706
+ | `edges` | Min/max count and status on related entities (e.g., fulfilments) |
1707
+
1708
+ **Polling mode:** Set `poll: true` to retry assertions on an interval until they pass or the timeout is reached. Useful after sending events when state changes are asynchronous.
1709
+
1710
+ **Example** — assert order is BOOKED with at least 1 fulfilment (polling):
1711
+
1712
+ ```json
1713
+ {
1714
+ "entityType": "ORDER",
1715
+ "ref": "HD-001",
1716
+ "assertions": {
1717
+ "status": "BOOKED",
1718
+ "edges": {
1719
+ "fulfilments": { "minCount": 1 }
1720
+ }
1721
+ },
1722
+ "poll": true,
1723
+ "timeoutMs": 60000,
1724
+ "intervalMs": 5000
1725
+ }
1726
+ ```
1727
+
1728
+ Response (pass):
1729
+
1730
+ ```json
1731
+ {
1732
+ "ok": true,
1733
+ "passed": true,
1734
+ "entityType": "ORDER",
1735
+ "entity": { "id": "12345", "ref": "HD-001", "status": "BOOKED" },
1736
+ "assertions": {
1737
+ "status": { "expected": "BOOKED", "actual": "BOOKED", "passed": true },
1738
+ "edges.fulfilments.minCount": { "expected": 1, "actual": 2, "passed": true }
1739
+ },
1740
+ "polling": { "attempts": 3, "elapsed": 12500 }
1741
+ }
1742
+ ```
1743
+
1744
+ Response (fail after timeout):
1745
+
1746
+ ```json
1747
+ {
1748
+ "ok": true,
1749
+ "passed": false,
1750
+ "entityType": "ORDER",
1751
+ "entity": { "id": "12345", "ref": "HD-001", "status": "CREATED" },
1752
+ "assertions": {
1753
+ "status": { "expected": "BOOKED", "actual": "CREATED", "passed": false }
1754
+ },
1755
+ "polling": { "attempts": 12, "elapsed": 60000, "timedOut": true },
1756
+ "failureMessage": "Assertion failed: status expected \"BOOKED\" but got \"CREATED\" (timed out after 60000ms)"
1757
+ }
1758
+ ```
1759
+
1760
+ ---
1761
+
1762
+ ## Retry and idempotency matrix
1763
+
1764
+ | Category | Tools | Retry behavior |
1765
+ |------|----------|-------------|
1766
+ | Read operations | `event.get`, `event.list`, `event.flowInspect`, `metrics.query`, `metrics.healthCheck`, `metrics.sloReport`, `metrics.labelCatalog`, `metrics.topEvents`, `workflow.transitions`, `graphql.query` (query), `graphql.queryAll`, `batch.status`, `batch.batchStatus`, `batch.results`, `graphql.introspect`, `connection.test`, `entity.get`, `environment.discover`, `environment.validate`, `test.assert`, `plugin.list` | retry + backoff |
1767
+ | Write operations | `event.send`, `batch.create`, `batch.send`, `graphql.query` (mutation), `graphql.batchMutate`, `entity.create`, `entity.update`, `workflow.upload`, `setting.upsert`, `setting.bulkUpsert` | timeout-only (no automatic retry) |
1768
+ | Local validation / no API | `config.validate`, `health.ping`, `event.build`, `webhook.validate`, `workflow.diff`, `workflow.simulate` | not applicable |
1769
+
1770
+ ## Tool inventory summary (36 tools)
1771
+
1772
+ | Tool | Category | Requires SDK | Retry | Description |
1773
+ |------|----------|:------------:|:-----:|-------------|
1774
+ | `config.validate` | Diagnostics | No | — | Validate auth/base URL configuration |
1775
+ | `health.ping` | Diagnostics | No | — | Quick health check with config summary |
1776
+ | `connection.test` | Diagnostics | Yes | Yes | Full auth + GraphQL end-to-end test (returns user/account context) |
1777
+ | `event.build` | Events | No | — | Build event payload (no API call) |
1778
+ | `event.send` | Events | Yes | No | Send event (async/sync, supports dryRun) |
1779
+ | `event.get` | Events | Yes | Yes | Get single event by ID |
1780
+ | `event.list` | Events | Yes | Yes | List/filter events with pagination and optional analysis mode |
1781
+ | `event.flowInspect` | Events | Yes | Yes | One-call root-entity flow forensics with compact/full modes |
1782
+ | `metrics.query` | Metrics | Yes | Yes | Query Prometheus metrics (instant/range) |
1783
+ | `metrics.healthCheck` | Metrics | Yes | Yes | One-call anomaly assessment with Prometheus + Event API fallback |
1784
+ | `metrics.sloReport` | Metrics | Yes | Yes | SLO snapshot with rates, latency, and threshold findings |
1785
+ | `metrics.labelCatalog` | Metrics | Yes | Yes | Label discovery for a metric (live sampling + known hints) |
1786
+ | `metrics.topEvents` | Metrics | Yes | Yes | Ranked event analytics using Event API aggregation |
1787
+ | `workflow.transitions` | Orchestration | Yes | Yes | Query available user actions/transitions at any entity state |
1788
+ | `plugin.list` | Orchestration | Yes | Yes | List registered rules with optional name filter |
1789
+ | `graphql.query` | GraphQL | Yes | Varies | Execute single-page query or mutation (retries for queries only) |
1790
+ | `graphql.queryAll` | GraphQL | Yes | Yes | Auto-paginated query — follows cursors across all pages |
1791
+ | `graphql.batchMutate` | GraphQL | Yes | No | Execute up to 50 aliased mutations in one request |
1792
+ | `graphql.introspect` | GraphQL | Yes | Yes | Schema introspection (types, mutations, queries) |
1793
+ | `batch.create` | Batch | Yes | No | Create ingestion job |
1794
+ | `batch.send` | Batch | Yes | No | Send records to job |
1795
+ | `batch.status` | Batch | Yes | Yes | Check job status |
1796
+ | `batch.batchStatus` | Batch | Yes | Yes | Check specific batch within a job |
1797
+ | `batch.results` | Batch | Yes | Yes | Get per-record job outcomes |
1798
+ | `webhook.validate` | Webhooks | No | — | Validate webhook payload + optional signature verification |
1799
+ | `entity.create` | Entity | Yes | No | Type-safe entity creation with field validation and gotcha knowledge (12 entity types) |
1800
+ | `entity.update` | Entity | Yes | No | Status-aware entity updates with optional transition validation |
1801
+ | `entity.get` | Entity | Yes | Yes | Unified entity lookup by ID or ref with optional edge inclusion |
1802
+ | `workflow.upload` | Workflow Mgmt | Yes | No | Deploy workflow JSON via REST API with structure validation |
1803
+ | `workflow.diff` | Workflow Mgmt | No | — | Compare two workflows — rulesets, statuses, risk assessment, mermaid output |
1804
+ | `workflow.simulate` | Workflow Mgmt | No | — | Static prediction of event outcomes from workflow JSON |
1805
+ | `setting.upsert` | Settings | Yes | No | Create or update a setting with upsert semantics |
1806
+ | `setting.bulkUpsert` | Settings | Yes | No | Batch create/update up to 50 settings with per-setting error handling |
1807
+ | `environment.discover` | Environment | Yes | Yes | Full environment snapshot — retailer, locations, networks, catalogues, settings, modules |
1808
+ | `environment.validate` | Environment | Yes | Yes | Pre-flight validation checks: auth, retailer, locations, inventory, workflows |
1809
+ | `test.assert` | Test | Yes | Yes | Assert entity state with status, attribute, and edge assertions + polling mode |
1810
+