---
name: fluent-transition-api
description: Query the Fluent Commerce Transition API to discover available user actions for entities at any workflow status. Enables dynamic test sequences and UI action validation. Triggers on "transition api", "user actions", "available actions", "workflow transitions", "what actions".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <entity-type> <subtype> <status> [--retailer-id <id>]
cursor-tier: auto
---

# Transition API — User Action Discovery

Query the Fluent Commerce User Action / Transition API to discover what actions are available for an entity at a given workflow status.

## Ownership Boundary

This skill owns Transition API queries, user action discovery, and action attribute mapping.

Workflow structure is owned by `/fluent-workflow-builder`.
Event dispatch is owned by `/fluent-mcp-tools`.
E2E test choreography is owned by `/fluent-e2e-test`.

## When to Use

- Discovering what user actions are available at a specific workflow status
- Validating that expected UI buttons/actions appear after workflow deployment
- Building dynamic test sequences that adapt to workflow changes
- Mapping required event attributes for each user action
- Auditing module-specific user action visibility

## API Reference

### Endpoint

```
POST <BASE_URL>/api/v4.1/transition
Content-Type: application/json
Authorization: Bearer <token>
```

### Request Body

```json
{
  "triggers": [
    {
      "type": "<ENTITY_TYPE>",
      "subtype": "<ENTITY_SUBTYPE>",
      "status": "<CURRENT_STATUS>",
      "retailerId": "<RETAILER_ID>",
      "module": "<MODULE_FILTER>",
      "flexType": "<ENTITY_TYPE>::<SUBTYPE>",
      "flexVersion": <VERSION_NUMBER>
    }
  ]
}
```

**Parameters:**

| Field | Required | Description |
|-------|----------|-------------|
| `type` | No | Entity type (ORDER, FULFILMENT, etc.) |
| `subtype` | No | Entity subtype (HD, CC, DEFAULT, etc.) |
| `status` | No | Current entity status. If omitted, returns actions for ALL statuses |
| `retailerId` | Yes | Retailer ID |
| `module` | **YES** | **MANDATORY.** Specific module name (case-sensitive): `adminconsole` (OMS App's module alias, legacy alias), `store`, `servicepoint` (Store App's module alias, legacy alias), or custom app suffix. **`"all"` returns NOTHING — never use it.** Without `module`, API silently returns empty results. |
| `flexType` | **YES** | **MANDATORY.** Workflow identifier: `<TYPE>::<SUBTYPE>`. Without `flexType`, API silently returns empty `response[]` — no error. **Auto-derived:** When omitted but `type` and `subtype` are present, the MCP tool auto-derives `flexType` as `{type}::{subtype}`. |
| `flexVersion` | **YES** | **MANDATORY in practice.** Without it, API may use cached stale version after recent deploys — causing false empty results. Always specify the exact version. |

**THREE MANDATORY PARAMETERS — omitting ANY one silently returns empty results (no error):**
1. **`module`** — Use `"adminconsole"` for the OMS App (the OMS App's module alias, legacy alias). `"all"` returns NOTHING.
2. **`flexType`** — Format: `"ENTITY_TYPE::SUBTYPE"` (e.g., `"ORDER::HD"`). **Auto-derived** by the MCP tool when omitted but `type`+`subtype` are present. For child entities, BOTH the standalone child workflow name (e.g., `"CREDIT_MEMO::APPEASEMENT"`) AND the parent workflow name (e.g., `"BILLING_ACCOUNT::CUSTOMER"`) work — use the matching `flexVersion` for whichever you specify.
3. **`flexVersion`** — Exact workflow version number. Without it, cached stale version may be used.

## Strict User Action Contract (Anti-Hallucination)

Transition API is read-only discovery. It does NOT create or modify workflow user actions.

When expected actions are missing, treat this as workflow/config drift and validate these non-negotiable contracts:

1. The workflow ruleset defines `userActions` inside the ruleset object (not at workflow root).
2. **Ruleset MUST have `subtype`**, and it MUST match the workflow's `entitySubtype`. Without `subtype`, the Transition API returns empty `userActions[]` — this is the #1 cause of invisible buttons and empty transition responses.
3. `userActions[].eventName` is explicitly set and aligned with the ruleset `name` unless intentionally different. If omitted, the platform infers it from the ruleset name.
4. `context[]` includes required fields: `label`, `type`, `modules`, `confirm`.
5. `context[].modules` values are determined by each app's `orchestrationAlias` in its root manifest (NOT the manifest setting name suffix): `"adminconsole"` (OMS App — manifest setting is `fc.mystique.manifest.oms`), `"servicepoint"` (Store App — manifest setting is `fc.mystique.manifest.store`), or a custom app's `orchestrationAlias`.
6. No invented schema keys are used in workflow JSON (for example: `actionLabel`, `moduleName`, `requiresConfirmation`, `buttonType`).
7. Transition trigger must be internally consistent: `type`, `subtype`, and `flexType` must refer to the same workflow.

### Module Name to UI App Mapping (Critical)

The `module` parameter in the Transition API trigger must match what the target UI app **actually sends** — which is NOT always the same as the app's display name.

| UI App Display Name | Transition API `module` Value | Why |
|---------------------|-------------------------------|-----|
| OMS App | `"adminconsole"` | OMS App manifest has `orchestrationAlias: "adminconsole"` (legacy alias) — overrides the module name sent to Transition API |
| Store App | `"servicepoint"` | Store App's module alias (legacy alias) |
| Admin Console (legacy, now OMS App) | `"adminconsole"` | Direct match |
| Custom App | suffix after `fc.mystique.manifest.` | e.g., `fc.mystique.manifest.warehouse` → `"warehouse"` |

**CRITICAL:** Using `module: "oms"` returns **empty `userActions`** for the OMS App. Always use `module: "adminconsole"` when querying or configuring actions for the OMS App.

To verify what module a UI sends: open browser DevTools → Network tab → navigate to entity detail → find `POST /api/v4.1/transition` → check the `module` field in request payload.

### Response

```json
{
  "response": [
    {
      "trigger": {
        "type": "ORDER",
        "subtype": "HD",
        "status": "BOOKED",
        "module": "adminconsole",
        "flexType": "ORDER::HD",
        "retailerId": "2"
      },
      "userActions": [
        {
          "eventName": "CancelOrder",
          "context": [
            {
              "label": "CANCEL ORDER",
              "type": "SECONDARY",
              "modules": ["adminconsole"],
              "confirm": true
            }
          ],
          "attributes": [
            {
              "name": "cancellationReason",
              "label": "Reason for cancellation",
              "type": "String",
              "mandatory": true,
              "source": "settings.cancellationReasons",
              "options": ["Customer request", "Out of stock", "Other"]
            }
          ]
        }
      ],
      "transitions": [
        {
          "eventName": "CancelOrder",
          "context": [...],
          "attributes": [...]
        }
      ]
    }
  ]
}
```

## Usage Patterns

Shell compatibility:
- The examples below are Bash-first.
- On Windows PowerShell, use `Invoke-RestMethod` equivalents (example below).
- When MCP extension tools are available, prefer `workflow.transitions` for cross-platform execution without manual token scripting.

```powershell
# PowerShell equivalent for Pattern 1 (auth + transition query)
$token = (Invoke-RestMethod -Method Post -Uri "$BASE_URL/oauth/token" -ContentType "application/x-www-form-urlencoded" -Body @{
  username = $FLUENT_USERNAME
  password = $FLUENT_PASSWORD
  client_id = $ACCOUNT
  client_secret = $SECRET
  grant_type = "password"
  scope = "api"
}).access_token

$headers = @{
  Authorization = "Bearer $token"
  "Content-Type" = "application/json"
}

$body = @{
  triggers = @(
    @{
      type = "ORDER"
      subtype = "HD"
      status = "BOOKED"
      retailerId = "2"
      module = "adminconsole"
      flexType = "ORDER::HD"
    }
  )
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Method Post -Uri "$BASE_URL/api/v4.1/transition" -Headers $headers -Body $body
```

### Parent-Child Entity Workflows & flexType

Child entities (CREDIT_MEMO, FULFILMENT, FULFILMENT_CHOICE) inherit `workflowRef` from their parent entity. The Mystique UI uses this `workflowRef` as the `flexType` in Transition API queries.

**Key finding (confirmed 2026-02-28):** The Transition API accepts BOTH the standalone child workflow name AND the parent workflow name as `flexType`. Use the matching `flexVersion` for whichever you specify.

**Both approaches work:**
- `flexType: "CREDIT_MEMO::APPEASEMENT"` + `flexVersion: "5.4"` (child workflow version) → returns user actions
- `flexType: "BILLING_ACCOUNT::CUSTOMER"` + `flexVersion: "1.8"` (parent workflow version) → returns same user actions

**What the UI does:** CreditMemo entities have `workflowRef: "BILLING_ACCOUNT::CUSTOMER"` → the UI sends the parent workflow as `flexType`. But for manual API queries and testing, either approach works.

**Example — querying CreditMemo actions (either flexType works):**
```json
{
  "triggers": [{
    "type": "CREDIT_MEMO",
    "subtype": "APPEASEMENT",
    "status": "PENDING_APPROVAL",
    "retailerId": 2,
    "module": "adminconsole",
    "flexType": "CREDIT_MEMO::APPEASEMENT",
    "flexVersion": "5.4"
  }]
}
```

Note: `type`/`subtype` are always the CHILD entity's values. `flexType` can be either the child's standalone workflow or the parent workflow — the Transition API resolves user actions by matching `type` + `subtype` + `status` within the specified workflow.

**Common mistake:** Omitting `flexVersion` — for CREDIT_MEMO and similar entity types, the API silently returns empty results without `flexVersion`, regardless of which `flexType` you use.

### Pattern 1: Discover Available Actions at a Status

> **Cross-platform note:** The REST API examples below use bash syntax. On Windows, use Git Bash, WSL, or the PowerShell equivalents shown earlier. The MCP `workflow.transitions` tool (shown in the PowerShell section above) is the recommended cross-platform approach.

```bash
# Authenticate
# IMPORTANT: Use dedicated variables (FLUENT_USERNAME, FLUENT_PASSWORD) instead of $USER,
# which collides with the Unix env var that contains the OS login name.
TOKEN=$(curl -s -X POST "$BASE_URL/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=$FLUENT_USERNAME&password=$FLUENT_PASSWORD&client_id=$ACCOUNT&client_secret=$SECRET&grant_type=password&scope=api" \
  | python -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Query transitions for ORDER::HD at BOOKED status
curl -s -X POST "$BASE_URL/api/v4.1/transition" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "triggers": [{
      "type": "ORDER",
      "subtype": "HD",
      "status": "BOOKED",
      "retailerId": "2",
      "module": "adminconsole",
      "flexType": "ORDER::HD"
    }]
  }' | python -m json.tool
```

### Pattern 2: Map All Actions Across All Statuses

Query without a `status` to get actions for every status in the workflow:

```bash
curl -s -X POST "$BASE_URL/api/v4.1/transition" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "triggers": [{
      "type": "ORDER",
      "retailerId": "2",
      "module": "adminconsole",
      "flexType": "ORDER::HD"
    }]
  }'
```

### Pattern 3: Validate Specific Module Actions

Filter by module to see only actions visible to a specific UI:

```bash
# Store App UI actions (module alias: "servicepoint", legacy alias)
curl -s -X POST "$BASE_URL/api/v4.1/transition" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "triggers": [{
      "type": "FULFILMENT",
      "subtype": "HD_WH",
      "status": "CREATED",
      "retailerId": "2",
      "module": "servicepoint",
      "flexType": "FULFILMENT::HD_WH"
    }]
  }'
```

### Pattern 4: Dynamic E2E Test Sequence

Use the Transition API to build adaptive test flows:

```
1. Create entity via graphql.query mutation
2. Query POST /api/v4.1/transition for current status
3. For each userAction in response:
   a. Extract eventName and required attributes
   b. Send event via event.send with extracted attributes
   c. Poll entity status until transition completes
   d. Query transitions again for new status
4. Repeat until no userActions
5. If `userActions` is empty, confirm whether status is terminal or system-managed before stopping
5. Report all transitions taken
```

This approach is more robust than hardcoded event sequences because:
- Test adapts automatically when workflow changes
- Discovers required attributes at runtime
- Validates that expected UI buttons exist
- Catches missing or unexpected user actions

### Pattern 5: Empty `userActions` Diagnostic Check

If transition response contains empty `userActions`, use this check:

1. **Verify ALL THREE mandatory parameters are present** — `module`, `flexType`, and `flexVersion`. Omitting ANY ONE of these silently returns empty results (no error message). This is the #1 cause of false empty results.
2. **`module`** must be a specific app name (`adminconsole` for the OMS App, `store`, `servicepoint` for the Store App). Do NOT use `module: "all"` — it returns NOTHING. For the OMS App, use `"adminconsole"` (NOT `"oms"`).
3. **`flexType`** must be the workflow name (e.g., `"ORDER::HD"`). For child entities, BOTH the standalone child workflow (e.g., `"CREDIT_MEMO::APPEASEMENT"`) and the parent workflow (e.g., `"BILLING_ACCOUNT::CUSTOMER"`) work — use the matching `flexVersion`.
4. **`flexVersion`** must be the exact deployed version. Without it, the API uses a cached "latest" that gets STALE after rapid deploys. Check via `workflow.get`.
5. Verify trigger filters are correct (`retailerId`, `subtype`).
6. Verify entity is actually in the status used in the trigger.
5. Verify the workflow ruleset has matching `subtype` and correctly placed `userActions`. Without `subtype` on the ruleset, user actions are invisible.
6. Verify `userActions[].eventName` and ruleset name alignment in workflow JSON.
7. Verify `context[].modules` values correspond to existing manifest settings (`fc.mystique.manifest.<module>` → `<module>`).
8. Check for workflowlog merge duplication: `fluent workflowlog describe -p <PROFILE> -r <RETAILER> <WORKFLOW_NAME>`. If multiple positions exist with the same ruleset name but different subtypes, delete old entries and redeploy.
9. **Verify module name matches what the app sends.** For OMS App buttons, use `module: "adminconsole"` (NOT `"oms"`). Check browser DevTools Network tab if unsure.
10. **For child entities, verify `flexType` and `flexVersion` are consistent.** Both the standalone child workflow name and the parent workflow name work as `flexType`, but `flexVersion` must match whichever workflow you specify. The UI uses the entity's `workflowRef` (which for child entities = parent workflow).
11. **Verify user actions are in the workflow matching your `flexType`.** User actions can be in either the standalone child workflow or the parent workflow — ensure they exist in whichever you're querying.
12. If still empty and status is non-terminal, treat as system-managed/gated phase and continue with workflow/event-based testing.

## Response Field Reference

### UserAction Fields

| Field | Type | Description |
|-------|------|-------------|
| `eventName` | String | Event to send when this action is invoked — maps to `event.send` name parameter |
| `context` | Context[] | UI display information (label, type, module visibility) |
| `attributes` | Attribute[] | Data required from the user when invoking this action |

### Context Fields

| Field | Type | Values | Description |
|-------|------|--------|-------------|
| `label` | String | e.g., "CANCEL ORDER" | Button label in UI |
| `type` | String | `PRIMARY`, `SECONDARY` | Button prominence |
| `modules` | String[] | e.g., `["adminconsole"]` | Which UIs show this action |
| `confirm` | Boolean | | Whether to show confirmation dialog |
| `style` | String | | CSS class hint for button styling |

### Attribute Fields

| Field | Type | Description |
|-------|------|-------------|
| `name` | String | Attribute name to attach to the event |
| `label` | String | UI form label |
| `type` | String | `String`, `Address`, `OrderItem`, `Product` |
| `mandatory` | Boolean | Whether this attribute is required |
| `value` | Object | Locked-in value (if present, don't show form field) |
| `defaultValue` | Object | Pre-filled default |
| `source` | String | Settings key for dropdown options. **MUST be lowercase** `"settings.<KEY>"` — capital `"Settings."` silently fails. Referenced setting MUST have `{ "active": [...] }` wrapper format and be available in the same retailer context used for the Transition query (or a deliberate ACCOUNT-level fallback). |
| `options` | Object | Populated from `source` — the valid values list |

## Mapping to MCP Event Dispatch

The Transition API response maps directly to MCP `event.send` calls:

```
userAction.eventName     → event.send name parameter
userAction.attributes    → event.send attributes parameter
trigger.retailerId       → event.send retailerId parameter
trigger.type + entityRef → event.send entityType + entityRef parameters
```

**Example:**

Transition API returns:
```json
{
  "eventName": "ConfirmShipment",
  "attributes": [
    { "name": "trackingNumber", "type": "String", "mandatory": true },
    { "name": "carrierRef", "type": "String", "mandatory": true }
  ]
}
```

Maps to:
```json
event.send({
  "name": "ConfirmShipment",
  "entityRef": "<FULFILMENT_REF>",
  "entityType": "FULFILMENT",
  "retailerId": "2",
  "attributes": {
    "trackingNumber": "TRACK-001",
    "carrierRef": "DHL"
  }
})
```

## Integration with Other Skills

| Task | Skill |
|------|-------|
| Analyze workflow structure | `/fluent-workflow-analyzer` |
| Build/edit workflow user actions | `/fluent-workflow-builder` |
| Send events discovered by transitions | `/fluent-mcp-tools` |
| Run full E2E test using transitions | `/fluent-e2e-test` |
| Debug failed transitions | `/fluent-trace` |

## Tips

- **THREE MANDATORY parameters** — `module`, `flexType`, and `flexVersion` are ALL required. Omitting any one silently returns empty results (no error).
- **Module names are case-sensitive** — `servicepoint` works, `ServicePoint` does not
- **NEVER use `module: "all"`** — it returns NOTHING. Always use a specific module name (`adminconsole` for the OMS App, `store`, `servicepoint` for the Store App)
- **Empty `userActions` array** can mean terminal, system-managed, or missing mandatory parameters (module/flexType/flexVersion)
- **`transitions` and `userActions`** often contain the same data — `userActions` is the canonical field
- **Locked `value` attributes** mean the workflow pre-sets the value (e.g., cancellation reason locked to "BROKEN") — do not show a form field for these
- **`flexVersion` is MANDATORY in practice** — without it, the API may use a cached stale version. Always specify the exact deployed version number.
- **Multiple triggers** can be sent in one request to batch-query actions for multiple entities/statuses
- **Use workflow JSON as the truth source for schema** — do not invent extra user-action keys; validate against `/fluent-workflow-builder` contract
- **All rules treated equally** — the platform merges `@EventAttribute` declarations from ALL rules in a ruleset regardless of plugin origin. There is no "OOTB vs custom" distinction.
