Skip to content
The docs are now organized by product area. Old links still work. Report a broken link

Query project data from the CLI

List and get suites, cases, steps, test plans, launches, defects, failure clusters and milestones with the Qualflare CLI, as JSON you can filter with jq.

Last updated

This page lists every read command of the Qualflare CLI with its flags and defaults: qf <identifier> <resource> list prints one page of 10 to 20 suites, test plans, launches, defects, failure clusters or milestones as JSON, and get prints one record by its number. cases list returns all of one suite's cases at once. The examples use the identifier myapp; replace it with the one you saved with qf login.

ResourceList commandGet commandsPage sizeMCP tools
Suitessuites listsuite get <seq>20list_test_suites, get_test_suite
Cases and stepscases list --suite <seq>case get <seq>, case steps <seq>whole suitelist_suite_cases, get_test_case, list_case_steps
Test plansplans listplan get <seq>, plan cases <seq>10list_test_plans, get_test_plan, list_test_plan_cases
Launcheslaunches listlaunch get <seq>10list_launches, get_launch
Defectsdefects listdefect get <seq>14list_defects, get_defect
Failure clustersclusters listcluster get <id>10list_failure_clusters, get_failure_cluster
Milestonesmilestones listmilestone get <seq>10list_milestones, get_milestone

How the read commands work

The Qualflare CLI read commands use the access token saved with qf login. A token can read without Allow writes, which is needed only to change data (Access Tokens). Each command prints pretty-printed JSON on stdout and its messages on stderr, so you can pipe stdout into jq. Coding agents read the same data through the MCP read tools.

Pages and sorting

Each paginated list returns {items, paging}. --page takes 1 to 1,000 and defaults to 1. --sort-by takes the fields each resource lists below, --sort-desc sorts in descending order and --sort-desc=false in ascending order. qf sends the direction only when you pass the flag, so each list keeps its own default order: launches and defects come back newest first, and in ascending order with --sort-by alone; clusters come back most failures first.

qf myapp suites list --page 2 --sort-by name
qf myapp suites list | jq '.paging'

The second command prints:

{
  "page": 1,
  "pages": 3,
  "perPage": 20,
  "totalItems": 45
}

Find a record's number

get takes the number Qualflare shows on the record, which is the seq of each list item. A failure cluster takes its id instead, which is also the end of the cluster's page URL.

The number Qualflare shows on a suite, case, test plan, launch, defect or milestone is the seq you pass to get. A failure cluster uses the ID in its URL.

Test suites

Test suites group test cases by feature or area. You see them under Browse, and qf <identifier> suites list lists 20 per page. See Test suites and Browse.

suites list

FlagTypeDefaultDescription
--querystring""Filter suites by name or description
--pageint1Page to return, starting at 1
--sort-bystring""Field to sort by: seq, name, priority, caseCount, teamName, coverageScore, createdAt or updatedAt
--sort-descboolfalseSort in descending order
qf myapp suites list --query "authentication"
qf myapp suites list --sort-by createdAt --sort-desc
qf myapp suites list | jq '.items[0] | {seq, name, priority, caseCount, coverageScore}'

The last command prints:

{
  "seq": 42,
  "name": "Authentication Tests",
  "priority": "high",
  "caseCount": 18,
  "coverageScore": 82
}

Each suite has seq, name, description, priority, automation, testType, coverageScore, caseCount, teamSeq, teamName, isArchived, createdAt and updatedAt.

suite get

qf myapp suite get 42 prints one suite, with the same fields as a list item.

Test cases and steps

Test cases belong to a suite, which you open under Browse, and hold ordered steps. cases list --suite <seq> returns the whole suite with a total count, so it has no --page flag. See Test cases: fields, states and steps.

cases list

FlagTypeDefaultDescription
--suiteint—Required. Suite sequence number to list cases from
--querystring""Filter cases by name (case-insensitive, partial match)
--statestring""Filter by one or more lifecycle states, comma-separated: active, review, outdated, draft. Pass and fail results belong to launches, not cases; see launches.
--prioritystring""Filter by one or more priorities, comma-separated: low, medium, high, critical
--sort-bystring""Field to sort by: seq, name, createdAt, updatedAt, flakyScore or position (the order in the suite)
--sort-descboolfalseSort in descending order
qf myapp cases list --suite 5 --state draft,review
qf myapp cases list --suite 5 --priority high,critical --sort-by flakyScore --sort-desc
qf myapp cases list --suite 5 | jq '{total, first: .items[0] | {seq, name, state, priority}}'

The last command prints:

{
  "total": 18,
  "first": {
    "seq": 101,
    "name": "User can log in with valid credentials",
    "state": "active",
    "priority": "critical"
  }
}

Each case has seq, position, uid, name, description, state, priority, testType, automation, isFavorite, isFlaky, flakyScore, mark, isArchived, suiteSeq, suiteName, createdAt and updatedAt. In cases list, suiteName is always empty; case get and plan cases fill it in.

case get

qf myapp case get 101 prints one case with the same fields, with suiteName filled in.

case steps

qf myapp case steps 101 prints the case's steps as an array, in order. Each step has id, instruction, stepType, position, origin, createdAt and updatedAt. Expected results are steps of their own, with stepType expected_result, and a step that comes from a shared step also carries a sharedStep object.

qf myapp case steps 101 | jq -r '.[] | "\(.position) \(.stepType) \(.instruction)"'
1 action Open the login page
2 expected_result The login form is displayed

Test plans

A test plan lists the cases to run for a release or testing cycle. You see test plans under Test Plans, and plans list shows 10 per page. See Test Plans.

plans list

FlagTypeDefaultDescription
--querystring""Filter plans by name (case-insensitive, partial match)
--pageint1Page to return, starting at 1
--sort-bystring""Field to sort by: seq, name or description
--sort-descboolfalseSort in descending order
qf myapp plans list --query "regression"
qf myapp plans list --sort-by seq --sort-desc

Each plan has seq, name, description, createdAt and updatedAt.

plan get

plan get adds caseSeq and querySeq. caseSeq lists the cases added to the plan, and querySeq the saved queries attached on its Dynamic Queries tab. plan cases returns the cases in caseSeq. Saved queries add no cases today; see Test Plans.

qf myapp plan get 5 | jq -c '{seq, name, caseSeq, querySeq}'
{"seq":5,"name":"v2.4 Regression Suite","caseSeq":[101,102,115],"querySeq":[2]}

plan cases

qf myapp plan cases 5 prints an array of the plan's cases, with the fields of cases list, except that isFlaky is always false and flakyScore always 0 here; read them with case get. state is the case's lifecycle state (active, review, outdated or draft), not a run result. To see how a plan's runs went, look at its launches: a launch run from a plan carries its testPlanSeq.

Launches

A launch is one test run. Each qf <identifier> collect creates one, sharded files merge into one, and manual runs and test plan runs are launches too. You see them under Launches, and launches list shows the newest first, 10 per page. See Launches.

launches list

FlagTypeDefaultDescription
--milestoneint0Filter by milestone sequence number
--environmentstring""Filter by environment Identifier (e.g., production, staging); see Environments
--pageint1Page to return, starting at 1
--sort-bystring""Field to sort by: seq, branch, platform, os, milestone, environment, language, browser, createdAt or updatedAt
--sort-descboolfalseSort in descending order

With no sort flags, launches come back newest first. With --sort-by and no --sort-desc, they come back in ascending order.

qf myapp launches list --milestone 3 --environment staging
qf myapp launches list --sort-by createdAt
qf myapp launches list | jq '.items[0] | {seq, status, branch, totalCount, passedCount, failedCount}'

The last command prints:

{
  "seq": 88,
  "status": "failed",
  "branch": "main",
  "totalCount": 111,
  "passedCount": 104,
  "failedCount": 2
}

Each launch has seq, framework, platform, os, branch, commit, status, automation, totalCount, passedCount, failedCount, errorCount, skippedCount, timeoutCount, abortedCount, flakyCount, retryCount, properties, environmentId, environmentName (the environment's display name), languageName, languageUid, milestoneSeq, milestoneName, browser, metadata, aiSummary, aiSummaryStatus, createdAt and updatedAt. A launch run from a test plan also has testPlanSeq.

launch get

qf myapp launch get 88 prints one launch, with the same fields as a list item.

Defects

Defects track the bugs found in testing. You see them under Defects, and defects list shows the newest first, 14 per page. See Defects.

defects list

FlagTypeDefaultDescription
--severitystring slice—Filter by severity. Accepts comma-separated values: critical, high, medium, low
--statusstring slice—Filter by status. Accepts comma-separated values: active, action_required, false_positive, closed, reopened
--pageint1Page to return, starting at 1
--sort-bystring—Field to sort by: seq, name, severity, status, category, environment, createdAt or updatedAt
--sort-descboolfalseSort results in descending order
qf myapp defects list --severity critical,high --status active
qf myapp defects list | jq '.items[0] | {seq, name, severity, status, category}'

The last command prints:

{
  "seq": 7,
  "name": "Login form fails with special characters",
  "severity": "high",
  "status": "active",
  "category": "assertion_failure"
}

Each defect has seq, name, severity, status, category, rootCauses, automation, isClosed, description, expected, actual, teamSeq, teamName, environmentId, environmentName, clusterId, clusterLabel, clusterStatus, clusterSeverity, closedAt, reopenedAt, reopenedReason, createdAt and updatedAt.

defect get

qf myapp defect get 7 prints one defect, with the same fields as a list item.

Failure clusters

A failure cluster groups failed results that share a root cause. You see them under Failure Clusters, and clusters list shows 10 per page, most failures first. See Failure Clusters.

clusters list

FlagTypeDefaultDescription
--severitystring slice—Filter by severity. Accepts comma-separated values: critical, high, medium, low
--pageint1Page to return, starting at 1
--sort-bystring—Field to sort by: failureCount (the default), firstSeenAt, lastSeenAt, severity or label
--sort-descbool—Results are descending unless you pass --sort-desc=false for ascending order

Clusters come back in descending order, by failureCount unless you pass --sort-by, and --sort-by alone keeps the descending order.

qf myapp clusters list --severity critical
qf myapp clusters list --sort-by lastSeenAt
qf myapp clusters list | jq '.items[0] | {id, label, failureCount, severity, status}'

The last command prints:

{
  "id": 15,
  "label": "NullPointerException in PaymentService.processRefund",
  "failureCount": 38,
  "severity": "critical",
  "status": "analysed"
}

Each cluster has id, label, summary, failureCount, severity, status, firstSeenAt, lastSeenAt, rootCause, fixSuggestion, createdAt and updatedAt.

cluster get

cluster get takes the cluster's id, not a seq, for example qf myapp cluster get 15. It adds sampleFailures, example failures with caseRunId, caseName, suiteName, errorText, similarity, launchSeq, caseRunSeq and suiteRunSeq. sampleFailures can be missing from clusters list items, so read it with cluster get.

Milestones

A milestone is a release or sprint target that launches link to. You see milestones under Milestones, and milestones list shows 10 per page. See Milestones.

milestones list

FlagTypeDefaultDescription
--querystring—Filter milestones by name (case-insensitive, partial match)
--pageint1Page to return, starting at 1
--sort-bystring—Field to sort by: seq, name, duesAt, createdAt or updatedAt
--sort-descboolfalseSort results in descending order
qf myapp milestones list --query "release" --sort-by duesAt
qf myapp milestones list | jq '.items[0] | {seq, name, kind, isCompleted, duesAt}'

The last command prints:

{
  "seq": 3,
  "name": "v2.0 Release",
  "kind": "release",
  "isCompleted": false,
  "duesAt": "2026-04-30T00:00:00Z"
}

Each milestone has seq, name, description, kind, isCompleted, totalCount, passedCount, failedCount, errorCount, skippedCount, timeoutCount, abortedCount, assignees, duesAt, completedAt, createdAt and updatedAt. kind is one of cycle, feature, iteration, plan, release, sprint or version. completedAt always holds a timestamp, even for an open milestone (it starts as the creation time), so check isCompleted to see whether a milestone is done.

milestone get

milestone get adds launchCount and runCount. List a milestone's launches with qf myapp launches list --milestone 3 (launches list).

Filter the output with jq

Every read command prints JSON, so jq can filter it, fail a CI job on it or loop over its pages:

# Fail a CI job while critical defects are open (jq -e exits 1 on false)
qf myapp defects list --severity critical --status active,action_required | jq -e '.paging.totalItems == 0'
# The status of the newest launch
qf myapp launches list | jq -r '.items[0].status'
# The pass rate of each launch on the page (null for a launch with no tests)
qf myapp launches list | jq '.items[] | {seq, passRate: (if .totalCount > 0 then (.passedCount / .totalCount * 100 | round) else null end)}'
# The flaky cases in a suite
qf myapp cases list --suite 5 | jq '[.items[] | select(.isFlaky) | {seq, name, flakyScore}]'
# A plan's cases by lifecycle state
qf myapp plan cases 5 | jq 'group_by(.state) | map({state: .[0].state, count: length})'
# The launches on the page that ran plan 5
qf myapp launches list | jq '[.items[] | select(.testPlanSeq == 5)]'
# The clusters with the most failures
qf myapp clusters list | jq '.items[] | {id, label, failureCount}'
# The milestones due after a date
qf myapp milestones list | jq '.items[] | select(.duesAt > "2026-04-01T00:00:00Z") | {seq, name, duesAt}'
# Every suite across all pages
pages=$(qf myapp suites list | jq '.paging.pages')
for p in $(seq 1 "$pages"); do qf myapp suites list --page "$p" | jq -c '.items[] | {seq, name}'; done

Errors

A read command that fails prints one Error: line on stderr and exits with a code your script can check:

What you seeWhyExit code
Error: no identifier "myapp" configured. Run 'qf login myapp <token>' to add it. (See 'qf projects' for the list.)No identifier of that name is saved on this machine1
Error: --suite flag is requiredcases list ran without --suite1
Error: get: <message> (status: 401) — run `qf login <identifier> <token>` to re-authenticateThe token is expired, revoked or wrong; see Access Tokens3
Error: get: … (status: 403) — the token lacks access to this project, or (status: 402) — a plan limit was reached; check your Qualflare subscriptionThe token can't read this project (403), or a plan limit was reached (402)4
Error: get: … (status: 404)No record with that number (or cluster ID) in the project5
Error: get: … (status: 400)A filter value the API rejects, or a --page above 1,0001
Error: get: … (status: 429), or a 5xx statusThe API is busy or failed; safe to retry7

Quote the [request_id: …] at the end of an API error when you contact support. --verbose prints the request line on stderr, and --debug logs the whole HTTP exchange with the token redacted.

Limits

These are flag value limits, not rate limits:

  • --page takes 1 to 1,000, and --query up to 255 characters.
  • --state, --priority, --severity and --status take only the values in the flags tables.
  • Each command waits up to 120 seconds for the API, set by QF_TIMEOUT.