TeamcenterKnowledge

Skills

TC Workflow Authoring

Skill tc-workflow-authoring. Author Teamcenter workflow templates and task handlers - over SOA (createOrUpdateTemplate, createOrUpdateHandler, getRegisteredHandlers, getSupportedHandlerArguments) and in Active Workspace Workflow Designer. Covers action vs rule handlers, trigger placement, the EPM-hold pattern that makes a workflow wait on an external job, and the access-level preference that silently blinds handlers. Use for any EPM/workflow template, handler, or process-instantiation work.

Workflow is the supported way to make Teamcenter do something when data changes. Handlers are the extension point — small programs bound to a task's actions. You almost never need to write one: several hundred ship OOTB, and two of them shell out to arbitrary scripts.

Discover before you author

getRegisteredHandlers is the first call in any workflow task. It answers the one question no documentation can: what is actually installed on this tier. Handlers arrive with solutions, not just with Foundation, so a handler documented in one guide may or may not be present.

POST <host>/tc/JsonRestServices/Workflow-2019-06-Workflow/getRegisteredHandlers
{}

The request struct has no members — an empty body is correct.

Then get the legal arguments for a specific handler rather than guessing from docs:

POST <host>/tc/JsonRestServices/Workflow-2020-01-Workflow/getSupportedHandlerArguments
{"input":[{"clientId":"c1","handlerName":"TSTK-CreateTranslationRequest","additionalData":[]}]}

clientId and handlerName are both REQUIRED attributes.

★★ This beats reading the handler documentation. It returns the real contract, including the mandatory/optional split that the Workflow Handlers guide does not state. Verified live against TC 2506:

{"mandatory":[{"-ProviderName":[]},{"-ServiceName":[]},{"-DatasetTypeName":[]}],
 "optional":[{"-Priority":[]}],"mutex":[],"required_one_of":[],"nullable":[]}

handlerData comes back as a JSON string inside the JSON response. Parse it twice.

TRAP, and it inverts the usual full-struct rule: do NOT send "additionalData":[]. An empty array for a member whose struct declares a REQUIRED @key attribute produces 214022 "An error has occurred during the JSON parsing". Omit the member entirely and the same call succeeds. Worth trying on any 214022 against a body that otherwise looks correct.

The operation map

Goal Route
What handlers exist here Workflow-2019-06-Workflow/getRegisteredHandlers
Legal arguments for a handler Workflow-2020-01-Workflow/getSupportedHandlerArguments
Create / update a template Workflow-2019-06-Workflow/createOrUpdateTemplate
Add / update a handler on a task template Workflow-2019-06-Workflow/createOrUpdateHandler
List templates Workflow-2013-05-Workflow/getWorkflowTemplates
Push edits into running processes Workflow-2010-09-Workflow/applyTemplateToProcesses
Start a process Workflow-2008-06-Workflow/createInstance
Act on a task (complete, approve, reject) Workflow-.../performAction, performAction2/3

Workflow-2008-06-Workflow/getWorkflowTemplates is DEPRECATED since 10.1 in favour of the 2013-05 namespace. Per the house rule in tc-soa-payload-shapes, a deprecated TC operation still returns 200 OK with an empty result forever. "No templates found" from the 2008-06 route is a version bug, not an empty site.

★★★ ...but the 2013-05 route ALSO returns an empty-looking result if the payload is underspecified: don't blame the route by default

Confirmed live on TC2606, 2026-08-07, and worth calling out because it looks identical to the deprecated-route symptom above and can send you chasing the wrong cause. GetWorkflowTemplatesInputInfo (from Workflow1305Workflow.xsd) has four REQUIRED attributes, and the request member is input (singular, not inputs):

attribute clientId                 required
attribute includeUnderConstruction required   -- boolean
attribute getFiltered              required   -- boolean
attribute group                    required
element   targetObjects            optional (soa:ModelObject)
element   objectTypes              optional  -- "not required if targetObjects specified"

Calling it with {} does not fault: it answers 200 OK with "templatesOutput": [], a well-formed answer to a request that specified nothing, indistinguishable at a glance from "this route is deprecated / this site has no templates." A tier with 170 real EPMTaskTemplate objects returned exactly this empty shape until the request carried all four required attributes:

{"input":[{"clientId":"probe","includeUnderConstruction":true,
           "getFiltered":false,"group":"dba"}]}

★★ includeUnderConstruction: true is essential when you just created the template. A template fresh out of Workflow Designer sits in Under Construction until the separate Set Stage → Available step (see below); includeUnderConstruction: false hides it, so a provisioning script that runs right after the manual step and used false would report "not found" even though the template genuinely exists.

The response shape does not match the obvious guess either. Top-level keys are .QName / ServiceData / templatesOutput, and templatesOutput is a one-entry array whose workflowTemplates member is the actual list of bare {uid, className, type} refs. Reading .templates or .output (either looks like a reasonable field name) silently returns nothing, even against a populated response. The refs carry no name, so a separate Core-2006-03-DataManagement/getProperties batch (try object_name, object_string, template_name; which one is populated was not settled with certainty) is required before you can match a template by name.

Diagnostic order when getWorkflowTemplates looks empty: (1) confirm the call didn't fault (.QName containing Exception), which is a different problem; (2) confirm you're on the 2013-05 route with all four required attributes and includeUnderConstruction: true; only after both of those check out should you conclude the template genuinely doesn't exist and the manual Workflow Designer step is actually needed.

⚠⚠⚠ RETRACTED 2026-08-08: the two sections below are WRONG. Read this first.

Everything about "you must use Workflow Designer" is retracted. Measured on the vanilla 2606 VM by capturing what AWC's own Workflow Designer posts, then reproducing it over SOA and reading the result back on a fresh session. The whole flow is scriptable, GUI-free, in three calls:

// 1. CREATE a PROCESS template (classification 0), stage 1 = Under Construction
POST Workflow-2019-06-Workflow/createOrUpdateTemplate
{"input":[{"clientID":"createProcessTemplate","templateName":"<name>","templateDesc":"<desc>",
"baseTemplate":"","additionalData":{"stage":["1"]},"parentTemplate":"","templateToUpdate":""}]}

// 2. HANDLERS onto the root Start action (unchanged, see the working body below)
POST Workflow-2019-06-Workflow/createOrUpdateHandler

// 3. PUBLISH it: stage 2 = Online/Available. THIS is "Set Stage to Available".
POST Workflow-2019-06-Workflow/createOrUpdateTemplate
{"input":[{"clientID":"saveEditTemplate","templateToUpdate":"<templateUid>",
"additionalData":{"template_stage":["2"]},
"templateName":"","templateDesc":"","baseTemplate":"","parentTemplate":""}]}

★★★ The key in step 3 is template_stage, NOT stage. That single name is why the earlier setProperties attempts on the stage property returned 200 and changed nothing, and why this was written off as a GUI-only state transition. The property is stage; the createOrUpdateTemplate key is template_stage. Step 1 uses stage. They are different keys in the same map, which is a genuinely nasty trap.

★★ additionalData is a flat JSON OBJECT ({"key":["value"]}), not the array of KeyValuesMap entries the XSD declares, exactly like createOrUpdateHandler. The XSD calls additionalData "reserved for future use", which is false and is what sent the 2506 investigation down the wrong path.

Process is what you get. A template created by call 1 comes back template_classification: 0 (Process), verified on a fresh session. The old claim that createOrUpdateTemplate can only make task templates was a misdiagnosis: those templates were almost certainly Process all along and simply stuck at stage 1, which is the same symptom the "stage is the real blocker" section below later identified independently.

Do not be misled by object_type. Every template, Process or Task, is an EPMTaskTemplate and AWC's header shows Type: Task Template. Read template_classification (0 = Process, 1 = Task), never the type name.

Proven end to end 2026-08-08: CSAInt CapitalForward Translate (AgIAAArpp$kOEC) authored entirely over SOA, classification Process, stage Online, Start action carrying TSTK-CreateTranslationRequest (-ProviderName=SIEMENS -ServiceName=CapitalForward -DatasetTypeName=Mdw0MDModel) plus EPM-set-owning-project-to-task, and confirmed visible to an ordinary business user's getWorkflowTemplates (170 templates before, 171 after).

The two sections that follow are kept for the reasoning trail only.

★★★ createOrUpdateTemplate makes a TASK template, NOT a process template (RETRACTED, see above)

The single most expensive thing to learn here, established on TC 2506 2026-08-04 with three independent proofs. A template built with createOrUpdateTemplate + createOrUpdateHandler is real, holds its handlers correctly, and opens in Workflow Designer — but it cannot start a process.

  1. Workflow-2014-10-Workflow/createWorkflow against it returns 200 OK with workflowTask = the NULLTAG uid AAAAAAAAAAAAAA, and later 219015 "The creation of workflow process has failed."
  2. ★★ Active Workspace's own Submit to Workflow dialog, with the template filter set to All, reports "No Matches" for it. That is the decisive test, because it removes your payload from the equation entirely.
  3. fnd0AllWorkflows and process_stage_list on the target revision stay empty.

Why: Workflow Designer asks you to pick a Template Type — Workflow or Task when creating a template. createOrUpdateTemplate has no parameter for that (only templateName, templateDesc, baseTemplate, parentTemplate, templateToUpdate), so it always produces a task template. Task templates are building blocks to drop into a process; they are not startable on their own.

Create the process template in Workflow Designer, then script the handlers onto it. createOrUpdateHandler against an existing, properly-typed template works fine, which is where the automation value actually is.

★ Red herrings ruled out along the way, so you do not repeat them:

  • WorkflowService has no publish/activate operation (all 29 checked), and createOrUpdateTemplate's additionalData is documented in the XSD as "reserved for future use".

★★★ stage genuinely IS the blocker — it just can't be flipped via setProperties

Correcting an earlier conclusion in this skill. A template built correctly in Workflow Designer (proper Process template type, tasks wired, handlers bound) still will not appear in File→New→Workflow Process in My Teamcenter, nor in Active Workspace's Submit-to-Workflow picker, while its stage is Under Construction. Confirmed 2026-08-05 on TC 2506 Rich Client, and it matches the product docs exactly (workflow_designer help, topic xid372331 and neighbors):

"Templates with the under construction designation are visible only to system administrators within Workflow Designer. They are not visible to end users who are using the File→New Process option in My Teamcenter... Select Set Stage to Available... In My Teamcenter, the Process Template list, within the New Process dialog box, displays the template name. All users at your site can now access the template."

The fix: select the template's root node in Workflow Designer → check the Set Stage to Available checkbox (only visible/present while stage is Under Construction; it disappears once Available) → Yes on the "Stage Change" confirmation dialog. This is a real, dedicated GUI-driven state transition, not a plain property write — which is exactly why the earlier setProperties attempt on stage correctly found "200 OK, no change." That attempt ruled out one wrong mechanism for the fix; it did not rule out stage as the actual cause. Both things are true at once: stage is the blocker, and you cannot flip it with setProperties.

Visible symptom to watch for: a template stuck Under Construction shows a yellow warning triangle next to its name in Workflow Designer's own Process Template dropdown. That triangle disappears the moment the stage switches to Available — a fast visual check before you go hunting for it in the (often enormous, hundreds of entries) New Process template picker.

The New Process template combobox is sorted case-sensitively, not alphabetically-as-a-human-reads-it. Uppercase letters sort before lowercase at the same position, so CR Process < CSAInt CapitalForward Translate < Cancel Supplier Declaration Request < ChangeItemRevision.... Scrolling to where you expect a name (case-insensitive intuition) will scroll right past it. The reliable shortcut: click into the combo's text field, select-all, type the exact template name, and stop — do not open/scroll the dropdown list at all. If the name is valid, the field keeps your typed text (rather than jumping to an unrelated match) and the Process Name field above auto-populates with <template>: <target>, which is itself proof the template was recognized. Mouse-wheel scroll on this combo moves in small, inconsistent increments even at large scroll_amount values (a giant site list can need 150+ scroll-units); dragging the scrollbar thumb is faster for big jumps but imprecise for small ones. Typing and skipping the list entirely avoids both problems.

★★★ Lockheed multi-tenancy requirement: EPM-set-owning-project-to-task

Per Lockheed direction (received 2026-08-06, multi-tenancy team): starting with their 8.3 release, every new workflow template must carry the EPM-set-owning-project-to-task action handler, or it will be flagged failing by the multi-tenancy validation team. It takes the owning project from the workflow's first target object and stamps it onto every workflow object (EPMTask, EPMJob, ...) so tasks stay visible only within their own program in a multi-tenant site.

  • Confirmed registered via getRegisteredHandlers and takes zero arguments via getSupportedHandlerArguments ({"no_arguments": true}) — nothing to configure beyond binding it.
  • Placement: the PROCESS TEMPLATE's own Start action — not a Do task's Start action. A process template root is itself an EPMTaskTemplate with its own lifecycle actions (Assign/Start/Perform/Complete/Promote/ Suspend/Resume/Abort/Undo), separate from any child Do task's actions. "The start task of every new workflow" means this root-level Start, which fires once per process instance regardless of which Do tasks exist inside — the correct place for a once-per-process stamp like this one.
  • ★★ In Workflow Designer's GUI, this root-level Start is easy to miss: click the process template's own root node in the tree (not a child task), then use the Handlers panel (found via the icon in the task-properties mini-toolbar whose tooltip reads "Displays properties" is adjacent — the actual Handlers trigger is the icon with tooltip "Task Attribute" / numbered-list icon, not the properties-dialog icon) to see Assign, Start, Perform, Complete, ... as the tree's top-level nodes.
  • The GUI "Create" button in that Handlers dialog was unreliable via remote/automated input in practice (clicks registered, no visible dialog or field enablement resulted, across many retries and even after confirming tooltip = "Performs a Create action"). The SOA route worked cleanly and is the reliable path: get the process template's own UID (its object_name matches the template name exactly, found via getWorkflowTemplates 2013-05, since EPMTaskTemplate objects for a 400+-template site have no fast name-search shortcut — batch getProperties across the returned uid list), then createOrUpdateHandler with action: 2 (Start) against that root uid — the same call shape documented above works unchanged for the process-root Start action, not just a Do task's.
  • Verify by reading the Start EPMAction's action_handlers back (either via getProperties on the action uid, or it comes back for free in the createOrUpdateHandler response's ServiceData.modelObjects) and confirming EPM-set-owning-project-to-task is now listed alongside whatever OOTB handlers (EPM-attach-related-objects, EPM-assign-team-selector, EPM-auto-assign-rest, ...) the base template already carried.

★★★ Who is allowed to actually START a process — two more traps

⚠⚠ THE TRAPS BELOW ARE TIER-SPECIFIC, NOT TEAMCENTER BEHAVIOUR. MEASURE BEFORE ASSUMING THEM. Everything in this subsection was observed on the cloud 2506 tier (Saber 2.0). Re-probed on the vanilla 2606 VM on 2026-08-08 and trap #2 is simply ABSENT there:

  • getWorkflowTemplates as an ordinary business user (ed, group Organization, role Designer) returned all 170 templates, not the empty list the cloud tier gives a comparable user.
  • The same user then successfully launched a process: Workflow-2008-06-Workflow/createInstance with processTemplate: "TCM Release Process" created an EPMJob and updated the target revision. No identity refusal at any point.
  • A first attempt with a mismatched template failed on 33127 / EPM-validate-target-objects ("Target object type 'Item Revision' is invalid ... -include_type=CompanyContact"), which is the useful negative control: the call reached handler execution, so authorization had already passed. A business-rule rejection and an identity rejection look nothing alike, so read the fault code before concluding you are blocked.

⇒ If a template picker is empty, that is a site configuration question for that tier, not a fact about Teamcenter. 33028 ("The template X cannot be found") is also just a name miss, not a permission problem.

startImmediately: false does NOT give you an inert dry run. On the VM the TCM Release Process ran anyway and left the target revision release_status_list = TCM Released. Consequence worth planning for: the launching user then could not delete their own object (51010 "does not have delete access"), because Access Manager denies delete on a released object. Cleanup needed a DBA. Probe on a throwaway item, and expect to need DBA to remove it afterwards.

infodba's launch refusal (#1 below) was not re-tested on the VM. It is an OOTB, username-keyed rule, so it probably does apply, but that is an inference and not a measurement here.

  1. infodba cannot create workflow process objects. Attempting to Submit to Workflow (any path) while logged in as infodba fails with: "Infodba is an installation user account and should not be used to create Workspace Objects. Login as a different user (besides infodba) and the workflow should be launched." This is enforced regardless of infodba's dba group membership — the restriction is keyed to the literal username, not the role. Use any other account to actually launch a process; infodba (or any DBA account) is still the right identity for authoring the template in Workflow Designer and for flipping the Available stage.
  2. A regular business user may see an EMPTY template picker even when templates exist and are Available. Confirmed live: logged in as a named Engineering.LM.US group user (role "Eng Lead"), the same New Process dialog's template combobox opened with zero entries — not filtered, not slow-loading, genuinely empty — while the same combobox for infodba listed hundreds of templates including the newly-Available one. This smells like a group/role/BMIDE-condition-based visibility restriction on template enumeration (the same class of mechanism as Awp0IsWorkflowTemplateAuthor for authoring, but this is a see-to-use gate, not an authoring gate), but the exact condition was not identified — unresolved as of 2026-08-05, needs a TC admin who knows this site's BMIDE conditions. Do not assume "logged in as a non-infodba user" is sufficient — it is necessary but was not observed to be sufficient here.
  3. Active Workspace's own Submit to Workflow dialog (both the compact version from a search-results quick-view, and the full one from an object's own page) showed a Template: label with a permanently blank, non-interactive value underneath — no dropdown arrow, no picker opened on click/double-click, and Tab-key focus order skipped over it entirely (Name field → Assignments tab, no stop at Template). This happened for both infodba and a regular user, so it is not the same gate as #2 above; it looks like a genuinely non-functional control in this AWC deployment, or one that requires a BMIDE-authored condition binding a default template to this object type before the field renders anything at all. Unresolved; the Rich Client path (My Teamcenter → File → New → Workflow Process, Ctrl+P) is the one proven to actually work end-to-end for template selection.

Workflow Designer may not be available to you at all. On a site where template authoring is DBA-gated, a non-DBA gets no Workflow Designer tile in the Launcher (#/workflowdesigner → "Missing Page") and "Start Edit" is disabled on template objects — even though the same account can create templates over SOA without complaint. Grant it via the BMIDE condition Awp0IsWorkflowTemplateAuthor (see below).

★ In the AW template toolbar the enabled command next to a disabled "Start Edit" is labelled "Online" but carries commandId Awp0TemplateSaveEdit — that is the End Edit. The label describes the online/offline mode, not the action.

Full-struct rule applies, hard

createOrUpdateHandler requires every one of these attributes:

clientID, handlerName, taskTemplate, businessRule, handlerType,
handlerToUpdate, action, ruleQuorum, changeExecutionOrder

createOrUpdateTemplate requires:

clientID, templateName, templateDesc, baseTemplate, parentTemplate, templateToUpdate

Omit one and you get a 200 OK that changed nothing. Send "" for the ones that do not apply (e.g. handlerToUpdate when creating), and the NULLTAG sentinel {"uid":"AAAAAAAAAAAAAA","type":"unknownType"} for an unused ModelObject member. See tc-soa-payload-shapes.

★★ The exact working createOrUpdateHandler body

Cracked by bisection on TC 2506 after five wrong shapes. Two things are non-obvious and each produces the same useless 214022 "error during JSON parsing":

{"input":[{
  "clientID": "h1",
  "handlerName": "TSTK-CreateTranslationRequest",
  "taskTemplate": "AovpX4A3Z$cfED",      // UID, NOT the template name
  "businessRule": "",                     // required key; only used for Rule handlers
  "handlerType": "Action",                // "Action" | "Rule" (case-insensitive)
  "handlerToUpdate": "",                  // empty => create
  "action": 2,                            // INTEGER, not "2"
  "ruleQuorum": 0,                        // INTEGER
  "changeExecutionOrder": 0,              // INTEGER
  "additionalData": {"-ProviderName":["SIEMENS"],
                     "-ServiceName":["CapitalForward"],
                     "-DatasetTypeName":["Mdw0MDModel"]}
}]}
  1. action, ruleQuorum and changeExecutionOrder are xsd:integer. Sending "2" / "0" as strings is a parse error. action values: Assign 1, Start 2, Complete 4, Promote 5, Suspend 6, Resume 7, Undo 8, Abort 9, Perform 100.
  2. ★★ additionalData is a plain JSON OBJECT of key → list-of-strings, even though the XSD literally declares KeyValuesMap with a key attribute and repeated value elements. The <xjb4c-hashmap> annotation is the tell: the JSON binding flattens it. [{"key":"-X","value":["y"]}] is a parse error.
  3. Every required attribute must be present even when meaningless — dropping businessRule for an Action handler is a parse error, not a default.

Verify by reading action_handlers back off the template; the arguments property returns them newline-joined, e.g. '-DatasetTypeName=Mdw0MDModel\n-Priority=2\n-ProviderName=SIEMENS\n'.

Verify shapes offline instead of guessing:

python scripts/soa_lookup.py createOrUpdateHandler

★★★ Driving a task instance directly: performAction3, and its silent-no-op trap

The working struct (Workflow-2014-06-Workflow/performAction3, full-struct rule applies as always):

{"inputs":[{"clientId":"c1",
  "actionableObject":{"uid":"<EPMTask uid>","type":"<its real type, e.g. EPMDoTask>"},
  "action":"SOA_EPM_complete_action",
  "supportingObject":{"uid":"AAAAAAAAAAAAAA","className":"unknownClass","type":"unknownType"},
  "propertyNameValues":{}, "signatures":[], "password":"", "supportingValue":""}]}

Action strings: SOA_EPM_start_action, SOA_EPM_complete_action, SOA_EPM_abort_action, SOA_EPM_assign_action (pair with supportingObject = the User/ResourcePool to assign), and others per the deprecated performAction's doc (same enum). A Condition task's decision goes in supportingValue, not propertyNameValues"True" was observed to work in one run; the officially documented tokens are "SOA_EPM_true" / "SOA_EPM_false", and a bare "" complete on a Do Task did NOT reliably work — prefer the documented "SOA_EPM_completed" token if a plain empty string doesn't take.

☠☠☠ EXERCISED 2026-08-24, and this cost most of a session: a rejected performAction3 call and a genuinely successful one are BYTE-IDENTICAL at the wrapper level — {"ok": true, "status": 200, "modelObjects": {}}, no error, no partialErrors, nothing. The only way to tell them apart is to re-read the target object's real_state/task_result afterward — this is not optional, it is the only signal that exists. Confirmed by direct comparison: a call that silently did nothing and a call that genuinely started/completed a task returned the exact same envelope; the working one additionally carried an unrelated "updated": ["<UserSession-ish uid>"] entry (a session-refresh side effect, not the target task), which is easy to mistake for evidence of success and is not.

What actually causes the silent no-op, in the order to check them:

  1. The performer. fnd0Performer on the task must match the connected user. A performer/responsibility mismatch is what Teamcenter's OWN Active Workspace UI reports loudly ("the current user is neither a responsible party nor an active surrogate for the task") — but performAction3 over SOA does not surface that same error; it just no-ops. If a task is stuck for SOA calls, try the identical action as the same user through AWC/RAC as a control before concluding anything else — a real permission message there, with silence over SOA, is itself diagnostic.
  2. EPM-hold on the task template's Complete action (the Do-task default from "The pattern for waiting on an external job" above). This one IS loud — a real fault, 33083 Business rules for handler 'EPM-hold' on action 'Complete'... are not met — so it's the easy case, not the trap. Unbind it via createOrUpdateTemplate with additionalData: {"complete_action_rules": []} against the CURRENT template generation's uid (see the Workflow-Designer-clones-on-save finding above — re-resolve first).
  3. A task type that genuinely needs more than a bare complete, e.g. EPMSelectSignoffTask — completing it plausibly needs an actual signoff team assigned first, not just the action call. Working through the assignee's own AWC inbox cleared several of these where the equivalent SOA call kept silently no-op'ing; that is real signal, not coincidence — whatever AWC's "complete" button does for these task types is not fully reproduced by the bare performAction3 call above.

Do not blame performAction3's error-reporting layer before checking the above. A generic-purpose SOA client wrapper (tc_client.py's _soa_error, in this workspace's tc-mcp) was patched this same day to surface partialErrors on a success-shaped envelope — a real, worthwhile fix for a DIFFERENT confirmed bug (createOrUpdateTemplate write-access-denied responses were carrying partialErrors that a naive ok: err is None check missed entirely) — but re-running the exact same failing performAction3 calls through the patched code showed the raw Teamcenter response for THIS symptom genuinely has zero error content anywhere. Two different bugs produced the identical-looking "did nothing" symptom in one session; fix the wrapper for the one you can prove has swallowed content, and keep looking for the other.

Action handlers vs rule handlers

Action handler Rule handler
Purpose Does something Gates something
Returns n/a EPM_go or EPM_nogo
Multiple targets acts on each ALL targets must pass (AND)
Combining sequential AND/OR, with rule quorums when OR'd

handlerType on createOrUpdateHandler selects which; ruleQuorum is only meaningful for rule handlers combined with OR.

Placement: the rules that cost the most time

  • Handlers bind to a task action (Start, Complete, Perform, …) and execute in order within a trigger group. changeExecutionOrder is how you reorder them.
  • A task's state does not become Started until every handler on the Start action has succeeded. So anything that depends on the task being started — most importantly an ACL indicated by EPM-set-rule-based-protection — must be placed on Complete, or on a successor task's Start. This is the documented reason the common "set protection then attach objects" recipe fails when both handlers sit on Start.
  • Argument values are case-sensitive and must use real BMIDE names, never display names.
  • Multiple values for one argument are comma-separated, unless a value itself contains a comma — then change EPM_ARG_target_user_group_list_separator.
  • Never put a heavyweight action handler on the Perform action of a perform-signoffs task: it runs once per signoff.

★★ Task-graph sequencing / successor wiring — genuinely hard, but not GUI-only

The arrows Workflow Designer draws between tasks (which successor to run next, Approve→X vs Reject→end) are not exposed by createOrUpdateTemplate / createOrUpdateHandler, are not a GRM relation, and do not appear anywhere in the public SOA catalog — confirmed 2026-08-15 by grepping every Workflow schema version shipped on a 2606 tier (Workflow0706Workflow.xsd through Workflow2001Workflow.xsd, all 10 additive versions) for successor, decision, graph, nextTask, connectTask: zero hits. getAllTasks (WorkflowService) looks promising by name but requires a live process ModelObject, not a template — useless for template authoring. expandGRMRelationsForPrimary against a real branching OOTB template ("TCM Release Process") returns empty relationship data: the graph is not GRM.

But the real property names exist and are confirmed real: dependency_task_templates and dependency_task_actions, on plain EPMTaskTemplate. They were not in any WSDL, any javadoc, or any documented property list — they surfaced only in the ServiceData.modelObjects payload of a live exportObjectsToPLMXML call (see below), then were independently re-confirmed with a plain getProperties call (came back as clean empty arrays, not silently dropped the way ~50 other guessed names were in the same session — successor_list, next_action, quorum_actions, decision_targets, etc. all silently vanish from a getProperties response when the name is wrong; a real-but-empty property comes back as [], which is how you tell the two apart).

Confirmed READ-ONLY via Core-2010-09-DataManagement/setProperties — attempting to set dependency_task_templates on a real task template returns "Property Dependency Task Templates is not modifiable." (code 38060), the same fault shape as the already-known-read-only fnd0RequiredParticipants. Generic property write is not the mechanism; this is workflow-engine-managed.

The credible scriptable path, not yet proven end-to-end: Siemens ships a purpose-built PLMXML export/import pair for whole workflow templates, evidenced by three real, tier-shipped TransferMode objects (found via tc_query_by_type("TransferMode"), which returns real business objects — a CHEAP way to discover configured transfer modes for practically any export/import task, not just this one): workflow_template_mode (export), workflow_template_import, workflow_template_overwrite. A read-only export was proven live:

GlobalMultiSite-2010-04-ImportExport/exportObjectsToPLMXML
  exportObjects=[{root process template uid}]
  transfermode={uid: "<workflow_template_mode uid on your tier>", type: "TransferMode"}
  revRule=NULLTAG, languages=[], sessionOptions=[]
  xmlFileName="probe.xml"
→ 200 OK, real xmlFileTicket + logFileTicket (FMS tickets)

This is a real, working, read-only extraction of an existing template (including at least one child task's full property set, which is how dependency_task_templates was found in the first place). It has not been carried through to: downloading the PLMXML via FMS (GET <host>/fms/fmsdownload/?ticket=<ticket>, needs live session cookies), reading how the dependency graph actually serializes, constructing an overlay for a target template, and importing it via importObjectsFromPLMXML with workflow_template_import (NOT _overwrite, which risks clobbering the whole template) against a test copy first. That is the concrete next step for whoever picks this up — genuinely open, not a dead end, but also genuinely unproven. Full trail: cameo-classification-libraries/ROUND3-CURATION-WORKFLOW.md §5.

If that path doesn't pan out, the GUI fallback is Rich Client's classic Process Designer — not Active Workspace. AWC's own workflow designer (see "Doing it in Active Workspace instead" below) authors templates and handlers but not graph/decision routing as far as this investigation found. RC is a desktop Java app, not a browser page, so the tc-capture-awc-calls network- interception recipe does not apply to it without separate investigation into whether RC's graph-editing traffic is even HTTP/JSON SOA under the hood.

★★★ The mechanism decoded, straight from real PLMXML — read this before re-guessing

Follow-up same day. Exported two real templates via exportObjectsToPLMXML (TransferMode workflow_template_mode) and downloaded the actual PLMXML over raw HTTP (no VM filesystem access needed — see the FMS recipe below). "TCM Release Process" turned out to have only one task, no branching (a bad first pick). "CN Process" (a real Change Notice workflow, 79 KB, 767 lines) is the one that showed the real pattern:

Each WorkflowTemplate element (EPMTaskTemplate) carries a flat dependencyTaskTemplateRefs="#idX #idY" attribute (this is what dependency_task_templates returns over SOA) plus a <UserData type="reference"> block with typed <UserValue title="..." value="N" dataRef="#idX"> entries giving each dependency its real semantic:

title value Meaning
startDependencyTaskRef 2 Normal predecessor → successor
completeDependencyTaskRef 4 Fires on the source task's Complete
failDependencyTaskRef 10 The reject/failure path
restartDependencyTaskRef 200 Loop-back / rework path
parentDependencyTaskRef 202 Link back to the containing task group

★★★ The name startDependencyTaskRef (code 2) is misleading — EXERCISED 2026-08-24 live, and code 2 gates on the predecessor's COMPLETE, not Start. A three-way fan-out into a Review task, all edges coded 2: read the review's real_state at 0/3, 1/3 and 2/3 predecessors CompletedPending every time, with all three predecessors themselves sitting at Completed (not merely Started) — and only Started the instant the third one reached Completed, timestamped to the second. Repeated successfully on a second, independently-built process template the same day. This is a Success/normal path (the "Success" arrow you draw in Workflow Designer, per its own help text — Fail paths are a distinct type), and it behaves like "wait for Complete," full stop, regardless of what the constant name implies. Do not infer task-gating semantics from this table's names alone; verify behaviourally on a real instance if the distinction matters for your design.

Real Approve/Reject/Rework branching is not a direct arrow. The pattern: a decision task (e.g. EPMConditionTaskTemplate) carries a WorkflowBusinessRuleHandler recording the outcome (-source_task=<task name> -decision=Approve / Reject / Rework). Downstream, dedicated gate tasks of type EPMOrTaskTemplate ("Approval Or", "Reject Or", "Rework Or") each carry startDependencyTaskRefs pointing back at every task whose decision could satisfy that path, and each gate's own Start action is bound to a rule handler checking -source_task=... -decision=<matching value>the same rule-handler EPM_go/EPM_nogo mechanism already documented above under "Action handlers vs rule handlers", not a new primitive. The "Or" gate is just an EPMTaskTemplate subtype whose only job is being that junction. Downstream of the gate, ordinary dependency refs chain to whatever comes next.

dependency_task_templates / dependency_task_actions (the plain EPMTaskTemplate properties) are confirmed read-only via generic setProperties — fault 38060, same shape as fnd0RequiredParticipants. Writing them needs the PLMXML import path below (unproven end-to-end as of this writing) or Workflow Designer.

The FMS download recipe (proven, no VM filesystem access needed)

exportObjectsToPLMXML (GlobalMultiSite-2010-04-ImportExport) returns an FMS xmlFileTicket — a real, working recipe to pull the actual file over raw HTTP, no VM access required:

  1. Bootstrap: GET <host>/ for the XSRF cookie (see tc-soa-session).
  2. POST <host>/tc/JsonRestServices/Core-2011-06-Session/login in the same cookie jar.
  3. GET <host>/fms/fmsdownload/?ticket=<ticket> in the same session — the ticket string is used exactly as returned (percent-escapes and all), dropped straight into the query string.

Every tier ships real, discoverable TransferMode objects for this — tc_query_by_type("TransferMode") returns real business objects (258 on this tier), including workflow_template_mode (export), workflow_template_import, workflow_template_overwrite. This is a cheap, generalizable trick for finding a configured transfer mode for any export/import task, not just workflow templates.

★★★ The real write mechanism is plmxml_import, not FMS upload at all

FMS upload (below) was the wrong path entirely — abandon it. plmxml_import is a server-side command-line utility that takes a local file path directly (-xml_file=), bypassing FMS tickets/upload completely. Doc: http://localhost:51000/en-US/doc/<collection>/<SKU>.utilities_reference/ plmxml_import — but that URL renders nothing by itself; the real content is inside an <iframe id="xhtml"> pointing at /documentation/external/<SKU>/en-US/tc_help/utilities_reference/<hash>/ plmxml_import.html. Read the iframe src via javascript_tool if the shell page looks empty — matches the documented tc-soa-docs-navigation "shell that renders nothing" trap, same disease, different symptom.

Working recipe (VM-side, via WinRM — see tc-vm-operations):

cmd /c "call C:\apps\PLM\tc_root\tc_menu\tc_Vanilla_Env.bat && plmxml_import ^
  -u=infodba -pf=C:\apps\PLM\tc_root\security\TCDB_infodba.pwf -g=dba ^
  -xml_file=<local scratch path, e.g. C:\Temp\claude-plmxml\x.xml> ^
  -transfermode=workflow_template_overwrite -ignore_originid ^
  -log=<local path>"

A password file (-pf=) already existed for infodba at that path on this tier — check for one before assuming you need to create it (-p= in plain text on a command line is exactly the "password as an explicit tool argument" anti-pattern). The XML file itself just needs to exist at an ordinary scratch path on the guest; no ticket, no FMS, no upload step.

Three real bugs, found and fixed in sequence, each with a genuinely different diagnostic — not a blind guessing loop:

  1. workflow_template_import is create-only. Per its own doc text: "If the template already exists in the database, the command is ignored." Updating an existing task silently no-ops under this mode — clean-looking log, Count:: N processed, nothing written. Use workflow_template_overwrite for anything that already exists.
  2. Origin-ID mismatch → fault 33314, loud and specific: "the importing templates do not match with the existing template(s)... retry after selecting the Ignore origin ID check option." Fix: add -ignore_originid (valid with overwrite; mutually exclusive with -apply_template, and -ignore_originid is NOT valid with plain workflow_template_import — that combination faults with a usage error before anything runs).
  3. Object lock → fault 515109 "The instance is in use." A live tc-mcp SOA session (or any other open session as the same user) holding the object blocks the CLI import. Fix: tc_disconnect that profile before running plmxml_import, tc_connect again afterward to verify.

★★★ ⚠⚠⚠ CORRECTED 2026-08-24: the claim below ("can only be written at CREATION time") is TOO BROAD and was disproven the same day it would have blocked real work. createOrUpdateTemplate's additionalData map — the SAME operation, not PLMXML — successfully rewired dependency_task_templates

  • dependency_task_actions on an already-existing, already-instantiated EPMDoTaskTemplate (three of them, part of a real Chris-built process template, not a disposable test object created in the same session):
{"input":[{"templateToUpdate":"<existing task template uid>",
"templateName":"<its current name>","templateDesc":"","baseTemplate":"",
"parentTemplate":"",
"additionalData":{"dependency_task_templates":["<new predecessor uid>"],
                  "dependency_task_actions":["2"]}}]}

Response had no partialErrors, ServiceData.updated named the exact object, and a follow-up getProperties on the SAME uid (not a new one) showed the new predecessor. A fresh createInstance afterward produced instances that followed the NEW wiring, and Arrange input's own dependency_task_templates (unrelated, never touched) was independently confirmed unchanged in the same read-back — a genuine edit, not a decoy object silently swapped in under the old uid.

The likely reconciliation with the PLMXML finding below, not yet fully nailed down: the PLMXML negative control specifically targeted objects created in the same investigation, moments earlier, via SOA, all under workflow_template_import/overwrite. What actually changed here was the route (createOrUpdateTemplate additionalData, not a PLMXML re-import) against a real production-shaped template someone had built and saved in Workflow Designer. Whether the deciding variable is the write mechanism, or that the earlier attempts happened to target freshly-SOA-created objects specifically, is genuinely unresolved — but "dependency wiring is read-only on an existing object, full stop" is now falsified. If createOrUpdateTemplate keeps working on your object, use it; it is far cheaper than the delete+recreate PLMXML dance below.

★★ Separately and unrelated to the above: every save in Active Workspace Workflow Designer clones the ENTIRE process template into a BRAND NEW EPMTaskTemplate object tree (new uids for the root and every task), rather than mutating the existing objects in place. EXERCISED 2026-08-24, watched across five consecutive saves of the same template: object_name stayed identical each time ("CSAInt Fan-Out Test v3") but uid and creation_date changed on every single save, for the root AND every child task/handler. Consequences:

  • Any uid you captured before the user's next save is stale. Re-resolve by name (or by launching a fresh createInstance, whose root_task always points at whatever is currently live) rather than trusting a uid across a human editing session.
  • A SOA write via createOrUpdateTemplate/createOrUpdateHandler against an OLD generation's uid still succeeds (200, no error) and still updates that object — it just isn't the one anything resolves to anymore, so the fix silently "doesn't take" from the user's point of view while every read-back you personally run keeps confirming it worked.
  • Dependency wiring set via the additionalData technique above does survive the user's next Workflow Designer save — confirmed by re-reading it off the NEW generation's uids after the user saved again for an unrelated reason (a handler argument fix). So this is safe to do mid-session even if the human is also actively editing the same template, as long as you re-resolve current uids after each of their saves rather than reusing yours.

★★★ ★★★ RESOLVED [dependency-at-creation-only claim, corrected above], after 8 attempts with two independent controls: the dependency graph can only be written at object-CREATION time, never applied to already-existing EPMTaskTemplate objects.

Positive control (works): a PLMXML fragment with no ApplicationRef (no existing uid to match — genuinely new objects), dependency wiring baked in from the start, imported via -transfermode=workflow_template_import (create mode) → re-exported by name (plmxml_export -template="<name>" — no uid needed, exports by workflow template name directly) to recover the real uids → read back over plain getProperties: populated exactly as authored. This also independently confirmed the property is genuinely real over SOA on a live production object (CN Process's "Approval Or" gate task read back non-empty the same way).

Negative control (never works): the identical real-shaped XML (full WorkflowAction/WorkflowHandler elements, matching originalId, correct stage) against task templates that already existed (created earlier via SOA createOrUpdateTemplate) — tried under workflow_template_overwrite with -ignore_originid, without it (origin ID matched natively, zero faults either way), across 6 separate attempts including one with a completely bare WorkflowTemplate+UserData shape and one matching the real export byte-for-byte. Every one wrote nothing, despite a clean "processed" summary every time — a silent no-op the log format cannot distinguish from a real write. Never trust the log's counts; always re-read the property.

Practical consequence: you cannot patch dependency wiring onto a task template that already exists by any PLMXML route found so far. To get real graph wiring on an existing template's tasks, they need to be deleted and recreated in one PLMXML import carrying the full structure (handlers + graph) from scratch — not edited in place. Whether Workflow Designer's GUI has some other, non-PLMXML write path for existing objects is still unconfirmed; everything here went through the documented CLI/SOA surface only.

★★★ This was carried through to a real production template ("Library Curation Review") 2026-08-18: deleted the 5 existing tasks + root via Core-2006-03-DataManagement/deleteObjects, recreated all 6 (plus 2 EPMOrTaskTemplate gates and a new terminal task, 9 objects total) in one workflow_template_import call. The full graph verified correctly over live SOA getProperties on every object — this is a real, working, independently-confirmed proof that the mechanism holds up on production data, not just disposable test objects.

⚠⚠ New anomaly found doing that, unresolved — check for it whenever a dependency edge targets an EPMConditionTaskTemplate. The import engine auto-synthesizes a WorkflowBusinessRuleHandler name="EPM-check-condition" (wrapped in a WorkflowBusinessRule, quorum -1) on the predecessor's own Start action, with arguments -source_task=<condition task name> -decision=true — never authored, confirmed real via direct SOA read of the action's rules property. Isolated with a real control: an equivalent test where the dependency target was a plain EPMTaskTemplate (not a condition task) has empty rules on the same action shape. So this fires specifically when the edge's target is EPMConditionTaskTemplate.

Whether -decision=true is harmless placeholder bookkeeping or a real gate that would deadlock the predecessor task is UNKNOWN — nothing in a normal design would ever set a literal true decision (real decisions are named values like Approve/Reject). Do not launch a workflow built this way against a real item until this is resolved — check via a disposable test launch, or find documentation/inspect in Workflow Designer, before trusting a template with this shape to actually run.

The FMS upload step: unresolved, do not re-guess blindly

★ Superseded by plmxml_import above — kept for the negative-result record in case a future capability genuinely needs FMS upload for something other than workflow templates.

Getting a write ticket (Core-2007-01-FileManagement/ getTransientFileTicketsForUpload) works fine. The multipart POST to /fms/fmsupload/ on port 3000 (the AWC gateway) does not — the byte-for-identical 400 UNKNOWN_TICKET_TYPE_1{null} Jetty error came back regardless of encoding (raw byte body, HttpClient MultipartFormDataContent as a form field or with explicit headers, ticket as a URL query param instead of a form field).

The fix for that part: use port 4544 (FSC direct), not 3000. Same VM, different code path — confirmed by a genuinely different error (MISSING_TICKET_0, not UNKNOWN_TICKET_TYPE). The working shape: ticket as a URL query parameter (POST http://<host>:4544/fms/fmsupload/?ticket=<ticket>), file as the only multipart part (field name fmsFile, no separate fmsTicket field at all) → returns 200 OK, reproduced on two independent fresh tickets.

★★ But that upload is still not enough on its own — importObjectsFromPLMXML (called through the normal SOA session on port 3000) faults 1073 "File <name> does not exist" even right after a confirmed 200 upload with a never-reused ticket. Working theory, unconfirmed: FSC session affinity — the raw HTTP client used for the port-4544 upload authenticates its own independent session, and if a transient-upload ticket is only resolvable by the FSC client identity that wrote it, tcserver's own FSC binding on the SOA side simply can't see it. Untested fix: perform the whole login → upload → import sequence through one shared HTTP/session context instead of mixing an ad-hoc client for upload with tc-mcp's own session for the SOA call. Per the anti-pattern in tc-soa-session ("never sweep candidate service paths blindly"), this was left here rather than guessed at further — next session should test the shared-session theory directly rather than trying more upload encodings.

★★ The pattern for waiting on an external job

The single most useful thing in this skill. To make a workflow block until some out-of-band process (Dispatcher translation, simulation run, external system) finishes:

Do task
  ├─ Start action    : <handler that launches the job>
  └─ Complete action : EPM-hold
  • EPM-hold checks the task's task_result property. If it is not Completed, it pauses the task. That is the only thing stopping a started task from auto-completing.
  • A Do task already has EPM-hold on Complete by default — that is how the Do Task dialog gets a chance to appear.
  • The external process, when it finishes, performs the Complete action on that task. The workflow resumes.

Siemens ships two integrations built exactly this way, which is what makes it a supported pattern rather than a trick:

  • DOCMGT-render-document-revision — the Dispatcher RenderMgtTranslator "sets the task state to Completed when the translation is successful".
  • CAE-simulation-process-launch-handler — its documentation instructs you to place EPM-hold on Complete "to stop the task from automatically completing when started".

For the Dispatcher case specifically, see tc-dispatcher-requests.

Escape hatches: running your own code without writing a handler

Two OOTB action handlers shell out. Prefer these over building an ITK handler.

EPM-invoke-system-action -command=<script> — writes workflow context to an XML file and passes it as -f <xml-file>. Perl helpers ship in TC_ROOT/bin/tc/Workflow.pm. The workflow continues or halts on the script's return code. Put the script in TC_ROOT/bin; an absolute path works but pins the template to a platform.

EPM-run-external-command -lov=<lov-name> — everything configured through a single LOV, so no template edit is needed to change behavior. Keywords:

Keyword Purpose
INPUT~Target= Which objects to operate on, e.g. $TARGET.(ItemRevision)
INPUT~Application= The command to run
INPUT~CallPerTarget=YES|NO Once per target (default) or once for all
INPUT~ExportPath= Enables exporting dataset files; pairs with DATA~DATASETS
INPUT~DataPath= Where the config and data files are written
CFG~key=%formatted string% Lines written to a config file, reachable as $CONFIG_FILE
ARG~ Arguments passed to the command, e.g. ARG~-cfg=%$CONFIG_FILE%
DATA~DATASETS=relation~types~refs Writes a data file listing datasets
INPUT~ErrorMsg1 / ErrorMsg2 User-facing failure messages (displayed in reverse order)

Exit 0 for success, non-zero for failure. The handler does not clean the export path — your application must. An export whose target file already exists fails silently to the syslog and continues.

★ The preference that silently blinds every handler

WRKFLW_access_level_for_handlers_execution

Value Effect
regular (default) Handlers run under normal ACLs, access rules and handler logic
system Handler default access is overridden; handlers get system access

It applies to all handlers collectively, not individually. When a handler cannot see or attach an object it clearly should, check this before touching Access Manager. The same handler and the same data behave differently on two tiers purely because of this one value.

Debugging: set the TC_HANDLERS_DEBUG environment variable to ALL, or to specific handler names. Supported by EPM-check-target-object, EPM-validate-target-objects, EPM-check-target-attachments, EPM-attach-related-objects, EPM-remove-objects, and EPM-run-external-command. Output goes to the syslog.

Doing it in Active Workspace instead

Everything above has a UI, and non-developers will use it, so know the path:

Create template — More Commands → New → Create Workflow Template. Type Workflow, unique name, optionally Based On an existing template.

Add tasks — Start Edit (choose online or offline editing) → Task Palette → drag → End Edit.

Add a handler — select task → Handlers tab → pick the trigger group → New Handler → Action or Rule → choose handler (list includes custom and other-solution handlers) → mandatory arguments appear automatically → Add Arguments for optional ones → Add.

Handlers can be copied between actions, between tasks, and between templates (target template must be in Edit mode).

Non-DBA authoring: modify the BMIDE condition Awp0IsWorkflowTemplateAuthor to name a group, then put users in that group. Non-DBA authors can attach existing ACLs but cannot create new ones.

⚠ Editing a template does not change processes already running. applyTemplateToProcesses (or the AW command of the same name) is a separate, deliberate step.

Verify like everything else in Teamcenter

Per tc-verify-and-cleanup: a 200 OK from createOrUpdateHandler is not evidence. Re-read the template with getWorkflowTemplates (2013-05 route) and confirm the handler is present under the expected trigger group with the expected arguments. If you created test templates, delete them by name at the end.

Related: tc-dispatcher-requests, tc-soa-payload-shapes, tc-soa-session, tc-verify-and-cleanup.


Generated from skills/tc-workflow-authoring/SKILL.md in the tc-automation-skills library, which is the canonical copy and also serves as the agent skill set for Teamcenter work.