Syncs automated test results to Zephyr Scale for Jira (formerly TM4J / SmartBear / Adaptavist): picks the product variant (Scale Cloud / Squad / Enterprise), authenticates with a long-lived API token as a Bearer header, opens a Test Cycle per build, posts executions via `POST /testexecutions` (or bulk JUnit via `/automations/executions/junit`), and maps test methods to Zephyr Test Cases via `@TestCaseKey`-style annotations. Use when the team's Jira test management is Zephyr Scale; for the Xray Jira app use xray-integration, for standalone TestRail use testrail-integration, and for over-time flakiness analytics rather than TCM sync use currents-integration.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
"Zephyr" disambiguates into three Jira test-management products that are not API-compatible - picking the right one is step zero:
| Product | Origin / current owner | Key API host pattern |
|---|---|---|
| Zephyr Scale (formerly TM4J) | Adaptavist -> SmartBear | https://api.zephyrscale.smartbear.com/v2/ |
| Zephyr Squad (the older one) | Atlassian -> SmartBear | https://prod-api.zephyr4jiracloud.com/connect/ |
| Zephyr Enterprise (server-only) | SmartBear | On-prem Jira; per-instance |
This skill covers Zephyr Scale Cloud as the primary path - it's the most-deployed Zephyr variant in 2026 and the one new projects pick. Notes for Squad / Enterprise are inline.
The official documentation is at support.smartbear.com/zephyr-scale-cloud/.
At the time of authoring (2026-05-05), the documentation site was
behind WebFetch limits (auth/region-gated content); the URL is the
canonical reference for real-browser navigation. Patterns below are
the stable shapes documented across the SmartBear KB and per-language
clients (zephyr-scale-python-client, the Postman collection
SmartBear ships, and the mgechev/zephyr-scale-cloud-cli community
client).
If the team uses Zephyr Squad, the endpoints + auth differ
significantly - see the Squad-specific REST API docs and the
distinct prod-api.zephyr4jiracloud.com host.
@TestCaseKey annotation), open a Test Cycle for the build,
and POST /testexecutions one result per test case.Zephyr Scale Cloud uses a long-lived API token (generated via "API Access Tokens" in the Zephyr Scale settings) sent as a Bearer token:
ZEPHYR_TOKEN=<long-lived-token>
curl -H "Authorization: Bearer $ZEPHYR_TOKEN" \
'https://api.zephyrscale.smartbear.com/v2/healthcheck'Unlike Xray Cloud, no JWT exchange step - the token is used directly.
The token is per-account, not per-project - guard it with the same care as a Jira admin credential.
Two patterns mirror the TestRail / Xray approach.
def test_TC1234_can_add_to_cart():
...test('can add to cart [TC1234]', async () => { /* ... */ });A regex extracts TC1234 (the Zephyr Scale Test Case key) at
sync time.
For Java / TestNG:
@Test
@TestCaseKey("PROJ-T1234")
public void canAddToCart() { /* ... */ }The @TestCaseKey annotation is provided by community adapters
(no first-party SmartBear annotation library at the time of writing);
a small custom JUnit extension reads the annotation and emits a
Zephyr-compatible JSON file alongside the JUnit XML.
# scripts/zephyr_sync.py
import os, requests
BASE = 'https://api.zephyrscale.smartbear.com/v2'
HEADERS = {
'Authorization': f"Bearer {os.environ['ZEPHYR_TOKEN']}",
'Content-Type': 'application/json',
}
PROJECT_KEY = os.environ['JIRA_PROJECT_KEY'] # e.g. "CALC"
def open_cycle(name, version=None):
r = requests.post(f'{BASE}/testcycles', headers=HEADERS, json={
'projectKey': PROJECT_KEY,
'name': name, # e.g. "Build #1234"
'plannedStartDate': iso_now(),
'description': f'Automated cycle for {os.environ.get("BUILD_VERSION", "")}',
'jiraProjectVersion': version, # optional Jira version ID
})
r.raise_for_status()
return r.json()['key'] # e.g. "CALC-R42"The returned key (e.g. CALC-R42) is the Test Cycle's identifier;
results land inside it.
Per the documented Zephyr Scale Cloud /testexecutions endpoint
shape (consistent across SmartBear KB versions):
def post_execution(cycle_key, test_case_key, status, comment=None,
actual_end_date=None, execution_time=None):
r = requests.post(f'{BASE}/testexecutions', headers=HEADERS, json={
'projectKey': PROJECT_KEY,
'testCycleKey': cycle_key,
'testCaseKey': test_case_key, # e.g. "CALC-T1234"
'statusName': status, # 'Pass' | 'Fail' | 'Blocked' | 'Not Executed'
'comment': comment,
'actualEndDate': actual_end_date, # ISO-8601
'executionTime': execution_time, # milliseconds
})
r.raise_for_status()
return r.json()statusName accepts the Zephyr-installed status names. For projects
with custom statuses, query /statuses?projectKey=...&statusType=TEST_EXECUTION
at script init to confirm the available names - don't hard-code beyond
the four built-ins (Pass, Fail, Blocked, Not Executed).
For a lighter path that skips per-execution POSTs, Zephyr Scale also
ingests a JUnit XML file directly via /automations/executions/junit
A Jest suite syncing one build to project CALC:
test('can add to cart [CALC-T1234]', ...).export ZEPHYR_TOKEN=... # from Zephyr Scale > API Access Tokens
export JIRA_PROJECT_KEY=CALC
npm test -- --reporters=jest-junitcycle = open_cycle("Build #1234") # returns e.g. "CALC-R42"
post_execution(cycle, "CALC-T1234", "Pass",
comment="green on CI", execution_time=1240)The execution lands inside cycle CALC-R42; open it in Jira to see
the Pass recorded against CALC-T1234. To sync the whole run,
extract every key from the JUnit XML and post with bounded
concurrency - see references/api-wiring.md.
Run the sync as an if: always() step after the test step so failed
runs still update Zephyr. The script parses junit.xml
(junit-xml-analysis), extracts Test Case keys, opens one Test Cycle
per build, and posts executions with bounded concurrency (keep
max_workers=5 to stay under the 60 req/min rate limit). Supply
ZEPHYR_TOKEN from CI secrets. The full GitHub Actions workflow, the
batch helper, folder / label organization, and the bulk JUnit XML
import alternative are in
references/api-wiring.md.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Targeting Zephyr Squad endpoints with Zephyr Scale auth | Different host, different auth model; immediate 401. | Confirm the variant (see the Overview variant table). |
Hard-coding statusName: "Pass" / "Fail" only | Custom statuses installed by the project break silently. | Query /statuses at init; cache the valid set. |
| Per-execution POST with 1000 tests, no concurrency | Single-threaded; 30+ minutes for a release run. | Bounded concurrency (see references/api-wiring.md). |
| Per-execution POST with unbounded concurrency | Trips rate limit (60/min); execution drops. | max_workers=5 (see references/api-wiring.md). |
| Reusing one Test Cycle across many builds | Cycle accumulates noise; release sign-off is unreadable. | One Cycle per build; Cycles can be archived per release. |
autoCreateTestCases=true in CI | Every renamed test creates a new Test Case; folder fills with orphans. | Pre-create Test Cases manually; sync references existing keys. |
| Treating the API token as session-scoped | Token is long-lived per-account; no refresh. | Store in CI secrets; rotate via Zephyr Scale settings, not per-run. |
https://support.smartbear.com/zephyr-scale-cloud/ - canonical
Zephyr Scale Cloud documentation portal (auth/region-gated;
consult in a real browser).https://support.smartbear.com/zephyr-scale-cloud/api-docs/ -
REST API reference for Scale Cloud.https://support.smartbear.com/zephyr-squad-cloud/ - Squad Cloud
reference (different product, different API).junit-xml-analysis - upstream
parser for the input the sync script consumes.xray-integration,
testrail-integration -
sibling test-management integrations with the same architecture
but different APIs.