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)
createOrUpdateTemplate makes a TASK template, NOT a process templateThe 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.
Workflow-2014-10-Workflow/createWorkflowagainst it returns200 OKwithworkflowTask= the NULLTAG uidAAAAAAAAAAAAAA, and later 219015 "The creation of workflow process has failed."- ★★ 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.
fnd0AllWorkflowsandprocess_stage_liston 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:
WorkflowServicehas no publish/activate operation (all 29 checked), andcreateOrUpdateTemplate'sadditionalDatais 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
getRegisteredHandlersand takes zero arguments viagetSupportedHandlerArguments({"no_arguments": true}) — nothing to configure beyond binding it. - ★ Placement: the PROCESS TEMPLATE's own
Startaction — not a Do task'sStartaction. A process template root is itself anEPMTaskTemplatewith 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_namematches the template name exactly, found viagetWorkflowTemplates2013-05, sinceEPMTaskTemplateobjects for a 400+-template site have no fast name-search shortcut — batchgetPropertiesacross the returned uid list), thencreateOrUpdateHandlerwithaction: 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
StartEPMAction'saction_handlersback (either viagetPropertieson the action uid, or it comes back for free in thecreateOrUpdateHandlerresponse'sServiceData.modelObjects) and confirmingEPM-set-owning-project-to-taskis 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:
getWorkflowTemplatesas an ordinary business user (ed, groupOrganization, roleDesigner) 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/createInstancewithprocessTemplate: "TCM Release Process"created anEPMJoband 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.
infodbacannot create workflow process objects. Attempting to Submit to Workflow (any path) while logged in asinfodbafails 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 ofinfodba'sdbagroup 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.- 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.USgroup 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 forinfodbalisted 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 asAwp0IsWorkflowTemplateAuthorfor 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-infodbauser" is sufficient — it is necessary but was not observed to be sufficient here. - 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 (Namefield →Assignmentstab, no stop at Template). This happened for bothinfodbaand 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"]}
}]}
- ★
action,ruleQuorumandchangeExecutionOrderarexsd:integer. Sending"2"/"0"as strings is a parse error.actionvalues: Assign 1, Start 2, Complete 4, Promote 5, Suspend 6, Resume 7, Undo 8, Abort 9, Perform 100. - ★★
additionalDatais a plain JSON OBJECT of key → list-of-strings, even though the XSD literally declaresKeyValuesMapwith akeyattribute and repeatedvalueelements. The<xjb4c-hashmap>annotation is the tell: the JSON binding flattens it.[{"key":"-X","value":["y"]}]is a parse error. - Every required attribute must be present even when meaningless — dropping
businessRulefor 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:
- The performer.
fnd0Performeron 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") — butperformAction3over 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. EPM-holdon 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 viacreateOrUpdateTemplatewithadditionalData: {"complete_action_rules": []}against the CURRENT template generation's uid (see the Workflow-Designer-clones-on-save finding above — re-resolve first).- 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 bareperformAction3call 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.changeExecutionOrderis how you reorder them. - ★ A task's state does not become
Starteduntil every handler on the Start action has succeeded. So anything that depends on the task being started — most importantly an ACL indicated byEPM-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 Completed —
Pending 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:
- Bootstrap:
GET <host>/for the XSRF cookie (seetc-soa-session). POST <host>/tc/JsonRestServices/Core-2011-06-Session/loginin the same cookie jar.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:
workflow_template_importis 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:: Nprocessed, nothing written. Useworkflow_template_overwritefor anything that already exists.- 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 withoverwrite; mutually exclusive with-apply_template, and-ignore_originidis NOT valid with plainworkflow_template_import— that combination faults with a usage error before anything runs). - 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_disconnectthat profile before runningplmxml_import,tc_connectagain 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_actionson an already-existing, already-instantiatedEPMDoTaskTemplate(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, whoseroot_taskalways points at whatever is currently live) rather than trusting a uid across a human editing session. - A SOA write via
createOrUpdateTemplate/createOrUpdateHandleragainst 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-holdchecks the task'stask_resultproperty. If it is notCompleted, it pauses the task. That is the only thing stopping a started task from auto-completing.- A Do task already has
EPM-holdon 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 DispatcherRenderMgtTranslator"sets the task state to Completed when the translation is successful".CAE-simulation-process-launch-handler— its documentation instructs you to placeEPM-holdon 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.