Warning
This document is for an in-development version of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.
Developing Against the GA4GH WES API
This is a developer-facing companion to the admin GA4GH docs. The admin docs cover configuring and verifying the service; this guide walks an API client through actually submitting and monitoring Galaxy workflow runs over the GA4GH Workflow Execution Service (WES) v1.0.0 wire protocol.
Every request below targets a Galaxy server and uses Galaxy’s workflow formats. The curl examples were exercised against a live development server; the responses shown are real (ids shortened/anonymized).
Example clients
If you would rather drive the flow below from a runnable tool than from raw curl, two small
reference clients walk exactly this sequence (discover → stage → submit → poll → outputs →
tasks). Both expose a subcommand-per-endpoint CLI plus a demo subcommand that runs the
whole example end to end, and both are published to PyPI so they run straight under
uv without installing anything:
export GXY_WES_API_KEY=...
uvx gxy-wes demo --galaxy-url http://localhost:8080 # requests-only
uvx gxy-wes-bioblend demo --galaxy-url http://localhost:8080 # BioBlend-backed
gxy-wes — a hand-rolled
requestswrapper with no dependency beyondrequests, so it stays dependency-light and reads as a literal transcript of the wire protocol.gxy-wes-bioblend — the same CLI surface, but every call is routed through a BioBlend
GalaxyInstance, showing the shape a BioBlend-based application would use.
Important
These two packages are illustrative examples, not production-supported projects — they exist to make this document executable and to show the API two different ways. Do not build a product on them. For real workflow-execution tooling use Planemo or talk to the Galaxy API through BioBlend directly.
How Galaxy maps onto WES
WES is workflow-engine agnostic. Galaxy implements it on top of its existing workflow invocation machinery, so the WES nouns map onto Galaxy nouns:
WES concept |
Galaxy concept |
|---|---|
|
|
|
An invocation step, or a single job within a collection-mapping step |
run |
Invocation outputs (HDAs / HDCAs), decorated with DRS URIs |
|
A Galaxy workflow format (see below) — not CWL/Nextflow/WDL |
workflow_type is one of Galaxy’s own two workflow formats:
gx_workflow_format2— the gxformat2 (“Format2”) YAML format. CWL-like, hand-authorable.gx_workflow_ga— the native.gaJSON format produced by Galaxy’s workflow editor.
workflow_type_version is currently accepted as a free-form string (e.g. "1.0.0"); the
type itself is what matters, and Galaxy will also auto-detect it from the workflow body
(class: GalaxyWorkflow ⇒ format2; a top-level steps/workflow key ⇒ .ga).
Base URL and authentication
All endpoints live under /ga4gh/wes/v1/. With a local dev server that is:
http://localhost:8080/ga4gh/wes/v1/
Every endpoint except service-info requires authentication. Galaxy uses its normal API
key auth — pass x-api-key: <key> (the WES spec’s bearer-token scheme is not used). Get a
key for an existing account with HTTP basic auth:
curl -s -u "you@example.com:password" \
http://localhost:8080/api/authenticate/baseauth
# {"api_key": "317ab17d..."}
Anonymous and inactive accounts are rejected on submission and on every run-scoped read.
1. Discover the service
service-info is public and advertises the supported formats, engine parameters, and
filesystem protocols.
curl -s http://localhost:8080/ga4gh/wes/v1/service-info | jq .
Trimmed to the WES-relevant fields (the real response also carries
system_state_counts, auth_instructions_url, tags, and the standard GA4GH
organization/version/environment keys):
{
"id": "localhost.wes",
"name": "Galaxy WES API",
"type": { "group": "org.ga4gh", "artifact": "wes", "version": "1.0.0" },
"workflow_type_versions": {
"gx_workflow_ga": { "workflow_type_version": ["1.0.0"] },
"gx_workflow_format2": { "workflow_type_version": ["1.0.0"] }
},
"supported_wes_versions": ["1.0.0"],
"supported_filesystem_protocols": ["http", "https", "file", "s3", "gs"],
"workflow_engine_versions": {
"galaxy": { "workflow_engine_version": ["1.0.0"] }
},
"default_workflow_engine_parameters": [
{ "name": "history_name", "type": "string", "default_value": "" },
{ "name": "history_id", "type": "string", "default_value": "" },
{
"name": "preferred_object_store_id",
"type": "string",
"default_value": ""
},
{ "name": "use_cached_job", "type": "boolean", "default_value": "false" }
]
}
2. Provide the workflow and its inputs
A WES RunRequest is multipart/form-data. The fields Galaxy honors:
Field |
Required |
Notes |
|---|---|---|
|
yes |
|
|
yes |
Free-form string, e.g. |
|
one of url/attachment |
A URL Galaxy can fetch ( |
|
one of url/attachment |
The workflow file uploaded inline. |
|
no |
JSON object of workflow inputs (see below). |
|
no |
JSON object of Galaxy-specific run options (see below). |
|
no |
Accepted but currently not persisted onto the invocation. |
|
no |
Accepted; informational. |
workflow_params — wiring up inputs
Galaxy workflow inputs are keyed by the workflow’s input label, and dataset inputs are
references to data that already exists in Galaxy. Galaxy invokes with inputs_by="name",
so the keys are the workflow input names.
A dataset input is a {"src": ..., "id": ...} reference:
{ "input1": { "src": "hda", "id": "1e8ab44153008be8" } }
src is hda for a single dataset or hdca for a dataset collection; id is the encoded
content id. Simple parameter inputs (integers, text, booleans) are passed as plain JSON
values under their input name.
WES has no data-staging endpoint of its own. You stage inputs through Galaxy’s normal API first (create a history, upload/fetch datasets) and then reference them here.
workflow_engine_parameters — Galaxy run options
A JSON object. Recognized keys (also advertised in service-info):
Key |
Effect |
|---|---|
|
Run into this existing history (encoded id). Otherwise a new history is created. |
|
Name for the auto-created history (default |
|
Object store for the run’s outputs. |
|
|
Referencing a workflow already in Galaxy: gxworkflow://
Galaxy adds a non-standard URL scheme so you don’t have to ship the workflow body on every
submission. Use it as workflow_url:
gxworkflow://<encoded_workflow_id> # the StoredWorkflow (latest version)
gxworkflow://<encoded_workflow_id>?instance=true # a specific Workflow instance
The workflow only has to be accessible to the caller — the same rule a normal invocation uses: owned by them, shared with them, published/importable, or the caller is an admin. Anything else is a 403.
With gxworkflow://, Galaxy skips the import step and invokes the referenced workflow
directly. workflow_type is still required by the form but is not validated against
the stored workflow in this case — the “must match the auto-detected type or 400” check
only applies to inline workflow_attachment / fetched workflow_url submissions.
3. Submit the run
Staging step (Galaxy-native, abbreviated) — create a history and upload one dataset:
KEY=317ab17d...; BASE=http://localhost:8080
HIST=$(curl -s -X POST "$BASE/api/histories" -H "x-api-key: $KEY" \
-H 'Content-Type: application/json' -d '{"name":"WES Tutorial"}' | jq -r .id)
HDA=$(curl -s -X POST "$BASE/api/tools/fetch" -H "x-api-key: $KEY" \
-F "history_id=$HIST" \
-F 'targets=[{"destination":{"type":"hdas"},"elements":[{"src":"pasted","paste_content":"line one\nline two\n","ext":"txt","name":"wes_input"}]}]' \
| jq -r '.outputs[0].id')
A minimal Format2 workflow (simple.gxwf.yml):
class: GalaxyWorkflow
name: Simple Workflow
inputs:
input1: data
outputs:
wf_output_1:
outputSource: first_cat/out_file1
steps:
first_cat:
tool_id: cat1
in:
input1: input1
Submit it (the WES POST /runs):
curl -s -X POST "$BASE/ga4gh/wes/v1/runs" -H "x-api-key: $KEY" \
-F "workflow_type=gx_workflow_format2" \
-F "workflow_type_version=1.0.0" \
-F "workflow_params={\"input1\":{\"src\":\"hda\",\"id\":\"$HDA\"}}" \
-F "workflow_engine_parameters={\"history_id\":\"$HIST\"}" \
-F "workflow_attachment=@simple.gxwf.yml;type=application/x-yaml"
{ "run_id": "33b43b4e7093c91f" }
The run_id is the encoded Galaxy invocation id. Batch invocations (multiple invocations
from one request) are rejected by WES.
4. Monitor the run
Abbreviated status:
curl -s "$BASE/ga4gh/wes/v1/runs/33b43b4e7093c91f/status" -H "x-api-key: $KEY"
# {"run_id":"33b43b4e7093c91f","state":"RUNNING"}
Full run log — outputs are decorated with DRS URIs so a client can hand them straight to the DRS API:
curl -s "$BASE/ga4gh/wes/v1/runs/33b43b4e7093c91f" -H "x-api-key: $KEY" | jq .
{
"run_id": "33b43b4e7093c91f",
"request": null,
"state": "COMPLETE",
"run_log": null,
"task_logs_url": "/ga4gh/wes/v1/runs/33b43b4e7093c91f/tasks",
"task_logs": null,
"outputs": {
"wf_output_1": {
"src": "hda",
"id": "417e33144b294c21",
"workflow_step_id": 30,
"drs_uri": "drs://drs.localhost:8080/hda-4dcf52aeee371f21"
}
}
}
Notes on the run log:
requestis alwaysnull— Galaxy does not persist the originalRunRequest, so it cannot be reconstructed.task_logsis intentionallynull(deprecated in the WES spec). Usetask_logs_url.Only HDA (single-dataset) outputs get a
drs_uri; collection (HDCA) outputs carry an encodedidbut no DRS URI.
State mapping
WES has a single flat State enum (QUEUED, INITIALIZING, RUNNING, COMPLETE,
EXECUTOR_ERROR, CANCELING, CANCELED, …). A Galaxy run is a WorkflowInvocation,
whose state is mapped onto that enum (GALAXY_TO_WES_STATE in services/wes.py):
Galaxy invocation state |
WES state |
Meaning |
|---|---|---|
|
|
Invocation created, not yet scheduling. |
|
|
Deferred inputs are being materialized. |
|
|
Ready for a scheduling iteration. |
|
|
Fully scheduled; jobs are running/queued. |
|
|
Scheduling done and every job reached a terminal state. |
|
|
The invocation itself failed to schedule. |
|
|
Invocation was cancelled. |
|
|
Cancellation requested, in progress. |
(anything unmapped) |
|
Fallback; should not occur for current states. |
Poll status (or the run log state) until the state is terminal: COMPLETE (success),
EXECUTOR_ERROR (failure), or CANCELED (cancelled). Keep polling while it is any of the
non-terminal states (QUEUED, INITIALIZING, RUNNING, CANCELING).
Why completed ⇒ COMPLETE, even when a job failed
This is intentional and important: a failed job does not make the run unsuccessful.
WES state describes the run (the invocation), not individual jobs. Galaxy’s completed
means scheduling finished and all jobs reached a terminal state (ok, error, skipped,
paused, …) — and a job ending in error is frequently part of a perfectly normal,
valid execution path. For example, a workflow can route a step through a filter-failed
collection operation precisely so that some jobs are allowed to fail and the failures are
filtered out downstream; the invocation still completes successfully.
So:
The only invocation state that maps to
EXECUTOR_ERRORis Galaxy’s ownfailed— i.e. the invocation could not be scheduled — not “some job errored.”Clients should not infer run failure from a non-zero per-task
exit_code. Per-taskexit_codes (from the tasks endpoint) are for inspecting individual steps, not for deciding whether the run as a whole succeeded.Determine run success from the run
state(COMPLETE); inspect outputs and per-task detail for finer-grained results.
5. Tasks (per-step / per-job detail)
task_logs_url points at a paginated task list. A task_id is the step order_index
(e.g. "0", "1"), or order_index.job_index (e.g. "1.0", "1.2") for the individual
jobs of a collection-mapping step.
curl -s "$BASE/ga4gh/wes/v1/runs/33b43b4e7093c91f/tasks" -H "x-api-key: $KEY" | jq .
{
"task_logs": [
{
"id": "0",
"name": "input1",
"exit_code": null,
"stdout": null,
"stderr": null
},
{
"id": "1",
"name": "first_cat",
"exit_code": 0,
"stdout": "/api/jobs/417e33144b294c21/stdout",
"stderr": "/api/jobs/417e33144b294c21/stderr"
}
],
"next_page_token": null
}
stdout / stderr are URLs to plain-text job-output endpoints
(GET /api/jobs/{job_id}/stdout and /stderr). Steps with no underlying job — input
steps, for example — have null exit_code, stdout, and stderr (as id: "0" shows
above). Fetch a single task with GET /ga4gh/wes/v1/runs/{run_id}/tasks/{task_id}.
Pagination
Both GET /runs and GET /runs/{id}/tasks use opaque keyset (cursor) tokens, not
offsets. Pass page_size (1–100, default 10); if more results exist the response includes
next_page_token, which you echo back as page_token on the next request. Do not try to
decode or construct tokens yourself.
curl -s "$BASE/ga4gh/wes/v1/runs?page_size=2" -H "x-api-key: $KEY"
curl -s "$BASE/ga4gh/wes/v1/runs?page_size=2&page_token=<next_page_token>" -H "x-api-key: $KEY"
GET /runs only ever lists the authenticated user’s own runs.
6. Cancel a run
curl -s -X POST "$BASE/ga4gh/wes/v1/runs/33b43b4e7093c91f/cancel" -H "x-api-key: $KEY"
# {"run_id": "33b43b4e7093c91f"}
This requests cancellation of the underlying invocation; the run then transitions through
CANCELING to CANCELED.
Errors
Errors come back as Galaxy’s standard JSON error body ({"err_msg": ..., "err_code": ...})
with a matching HTTP status. The ones a client is most likely to hit:
Situation |
Status |
|---|---|
Missing/invalid required form field (e.g. no |
400 |
|
400 |
Malformed |
400 |
Submit while anonymous |
403 |
Submit while logged in but inactive (unactivated account) |
403 |
Run / task id does not exist |
404 |
Invalid |
404 |
Access to resources you do not own is not reported uniformly — watch for this:
Reading a run you don’t own →
403(AuthenticationRequired).Submitting against a history you don’t own →
404(ObjectNotFound).A
gxworkflow://reference to a workflow you cannot access (not owned, not shared with you, not published) →403(ItemAccessibilityException).
So a 404 on submission can mean “your history id is wrong” or “that history belongs to
someone else”; don’t assume 404 always means the object is absent.
Endpoint summary
Method |
Path |
Auth |
Purpose |
|---|---|---|---|
GET |
|
public |
Capabilities and supported formats |
POST |
|
required |
Submit a run ( |
GET |
|
required |
List the user’s runs (keyset paginated) |
GET |
|
required |
Full run log + DRS-decorated outputs |
GET |
|
required |
Abbreviated |
POST |
|
required |
Request cancellation |
GET |
|
required |
Paginated task list |
GET |
|
required |
Single task detail |
Known limitations and gotchas
Run
COMPLETEreflects the invocation, not individual jobs — by design, a failed job does not make the run fail (a failed job can be a normal, valid path, e.g. filter-failed). Only an invocation that fails to schedule maps toEXECUTOR_ERROR. Do not infer run failure from a per-taskexit_code. See §4.Inputs must be pre-staged — there is no WES upload endpoint; stage datasets via the Galaxy API and reference them by
{src, id}inworkflow_params.requestis never returned in the run log — the originalRunRequestis not stored.DRS URIs only on HDA outputs, not on collection outputs.
tagsare not persisted to the invocation, and run summaries returntags: {}.service-infoauth_instructions_urlis a placeholder ("TODO").The generated Pydantic models under
lib/galaxy/schema/wes/are produced bygen.shfrom the upstream GA4GH OpenAPI spec with no Galaxy post-processing — treat them as regenerable and avoid hand edits.
Source pointers
Router:
lib/galaxy/webapps/galaxy/api/wes.pyService:
lib/galaxy/webapps/galaxy/services/wes.pyGenerated models:
lib/galaxy/schema/wes/(regenerate viagen.sh)Shared GA4GH service-info builder:
lib/galaxy/webapps/galaxy/services/ga4gh.pyAPI tests (worked examples for every endpoint):
lib/galaxy_test/api/test_wes.py