Quick Start: Author Your First UWS Workflow
This guide is for authors who need to hand-write a UWS document without reading the full specification first. It focuses on the common path: bind existing source operations, put them in a workflow, pass values between steps, and leave concrete credentials to the runtime.
UWS is a workflow overlay. Source documents such as OpenAPI, Google Discovery, AWS Smithy, AsyncAPI, GraphQL, OpenRPC, gRPC/protobuf, and OData own HTTP methods, paths, channels, messages, schemas, RPC methods, services, metadata, servers, and security. UWS owns operation binding, workflow structure, request values, outputs, triggers, and control flow.
Start With Three Files
Keep the source contract, workflow, and runtime configuration separate:
openapi/
support.yaml
google-discovery/
gmail.json
asyncapi/
billing-events.yaml
graphql/
issue-tracker.graphql
openrpc/
pet-rpc.json
protobuf/
inventory.proto
odata/
service-metadata.xml
workflow.uws.yaml
runtime-config.json # runtime-owned, not UWS core
The UWS document points to source documents. It does not copy endpoint URLs, channels, schemas, or credentials out of the source document.
Minimal Workflow
uws: "1.6.0"
info:
title: Support Ticket Workflow
version: "1.0.0"
sourceDescriptions:
- name: support_api
url: ./openapi/support.yaml
type: openapi
operations:
- operationId: create_ticket
sourceDescription: support_api
sourceOperationId: createTicket
request:
body:
subject: "Example ticket"
priority: normal
outputs:
ticket_id: "$response.body.id"
- operationId: get_ticket
sourceDescription: support_api
sourceOperationId: getTicket
dependsOn: [create_ticket]
request:
path:
ticketId: "$steps.create.outputs.ticket_id"
outputs:
status: "$response.body.status"
workflows:
- workflowId: main
type: sequence
steps:
- stepId: create
operationRef: create_ticket
- stepId: verify
operationRef: get_ticket
This document declares:
- One OpenAPI source named
support_api. - Two UWS-local operations,
create_ticketandget_ticket. - One workflow named
main. - Two sequence steps, where
verifycan refer to the output of stepcreate.
Step 1: Declare Sources
Use sourceDescriptions for every API or event source document the workflow references:
sourceDescriptions:
- name: support_api
url: ./openapi/support.yaml
type: openapi
Rules of thumb:
nameis the stable local handle used by operations.urlcan be a local path or reviewed remote location, depending on the runtime.typemay beopenapi,google-discovery,aws-smithy,asyncapi,graphql,openrpc,grpc-protobuf,odata,browser-profile, oransible-module. Missingtypedefaults toopenapi. Thebrowser-profilesub-spec is published separately asversions/browser.1.5.{json,md}, and theansible-modulesub-spec asversions/ansible.1.0.{json,md}.
Step 2: Bind Operations
The preferred operation binding uses sourceDescription plus the generic sourceOperationId:
operations:
- operationId: create_ticket
sourceDescription: support_api
sourceOperationId: createTicket
operationId is local to the UWS file. sourceOperationId is the operation ID from the referenced source document. For AsyncAPI sources, it names a root AsyncAPI Operation Object. For GraphQL, OpenRPC, gRPC/protobuf, and OData sources, source-aware tooling defines stable operation identifiers or refs. For Ansible module sources, it is the module's fully qualified collection name (FQCN), such as ansible.builtin.apt. OpenAPI sources may also use legacy openapiOperationId.
Do not add HTTP method, path, channel, server URL, or security configuration here. Those belong to the source document and the bound runtime.
Ansible module sources follow the same binding shape; module arguments go under request.body:
sourceDescriptions:
- name: builtin
url: ./ansible/ansible-builtin.argspec.json
type: ansible-module
operations:
- operationId: install_nginx
sourceDescription: builtin
sourceOperationId: ansible.builtin.apt
request:
body:
name: nginx
state: present
outputs:
changed: $response.body.changed
Step 3: Add Request Values
Request values are grouped by common source request location:
request:
path:
ticketId: "$steps.create.outputs.ticket_id"
query:
includeHistory: "true"
header:
X-Request-Source: review
body:
priority: normal
Use runtime expressions when a value comes from a previous step, workflow input, trigger, variable, or response.
For AsyncAPI source operations, use body for the message payload or operation input values and header for AsyncAPI Message Object headers values when the source operation defines them.
For GraphQL, OpenRPC, gRPC/protobuf, and OData source operations, use the same request binding object and leave protocol-specific serialization to source-aware tooling and the bound runtime.
Step 4: Name Outputs
Outputs turn response values into stable handles:
outputs:
ticket_id: "$response.body.id"
status: "$response.body.status"
Common expression roots:
$response.body: response body from the current operation.$response.headers: response headers from the current operation.$steps.<stepId>.outputs.<name>: output from a previous step in the same workflow.$inputs.<name>: workflow input.$variables.<name>: reusable document variable.$trigger: trigger payload during trigger dispatch.
Keep expressions simple unless your runtime documents extensions beyond UWS core.
Step 5: Compose Workflows
A sequence workflow runs steps in order:
workflows:
- workflowId: main
type: sequence
steps:
- stepId: create
operationRef: create_ticket
- stepId: verify
operationRef: get_ticket
Use the six structural types when needed:
sequence: run steps in declaration order.parallel: run independent steps concurrently.switch: choose a branch.loop: repeat over items.merge: join control-flow branches.await: wait until a runtime expression becomes true.
Start with sequence. Add other constructs only when the workflow needs them.
Add A Branch
workflows:
- workflowId: classify_ticket
type: switch
cases:
- name: closed
when: "$steps.verify.outputs.status == \"closed\""
steps:
- stepId: archive
operationRef: archive_ticket
default:
- stepId: notify_owner
operationRef: notify_owner
Switch cases contain branch steps. Keep branch conditions in the core expression grammar when portability matters.
Add A Trigger
triggers:
- triggerId: new_ticket
outputs:
- accepted
routes:
- output: accepted
to: [main]
Trigger ingress, authentication, hosting, output classification, and persistence are runtime-owned. UWS only describes declared output labels and route dispatch after a trigger event has been accepted.
Extension-Owned Operations
If a step is not a source-bound operation, make that explicit with an extension profile:
operations:
- operationId: render_message
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
type: fnct
function: render_message
arguments:
- template: "Ticket {{ticket_id}} is ready"
UWS validates the shape and references. The bound runtime decides whether it supports the named extension profile.
When you use the public uws.runtime.1.0 supplement, x-uws-runtime.type is required and must be one of the supplement's non-HTTP runtime selectors. Use source operation bindings for HTTP and event calls; do not model HTTP as x-uws-runtime.type: http.
Validation Checklist
Before handing a workflow to a runtime:
- Every
sourceDescriptionname is unique. - Every source-bound operation names an existing source.
- Every local
operationId,workflowId, and stepstepIdis unique where required. - Every
operationRef,workflow, trigger route, and dependency target resolves. - Request values do not contain secrets.
- Credentials and endpoint policy are supplied by the runtime, not embedded in UWS.
- Extension-owned operations declare an explicit
x-uws-operation-profile. uws.runtime.1.0operations includex-uws-runtime.type; HTTP and event calls remain source-bound.