Bitbucket Pipelines integration
API testing in Bitbucket Pipelines
The first-party Bitbucket pipe is public in source and awaiting image publication. Shift-Left API's public REST API lets you trigger test packs, gate merges on pass-rate thresholds, and pull results into any pipeline today.
The Shift-Left API Bitbucket pipe is not on Docker Hub yet, so a pipe: totalshiftleft/shiftleft-test-pack reference will not resolve today. Its source is public — see bitbucket-pipe/. Everything below is a real, working pattern using the public REST API directly.
What this integration gives you
The step below runs a Shift-Left API test pack as part of any Bitbucket Pipelines pipeline, using nothing but curl and jq against the public REST API: authenticate, trigger a run, and check the result. Use it to gate pull requests on API quality, run scheduled regression suites, or chain into broader release pipelines on Bitbucket Cloud.
Quality gates on pull requests
Exit non-zero when the pass rate misses your threshold, integrating cleanly with "Require successful builds" merge checks.
JSON results
The execution endpoint returns structured JSON; pipe it into your own reporting or artifact upload step.
Workspace variable scoping
Share credentials across repos via Workspace variables; per-repo overrides via Repository variables.
Self-hosted runner support
Run on Bitbucket cloud runners or your own self-hosted runners for air-gapped Shift-Left deployments.
bitbucket-pipelines.yml example
Drop this into your bitbucket-pipelines.yml. Runs on every pull request and on main-branch merges, and fails the step when the pass rate drops below 95%.
image: atlassian/default-image:4
pipelines:
pull-requests:
'**':
- step:
name: Shift-Left API Tests
script:
- apt-get update -qq && apt-get install -y -qq jq
- |
set -euo pipefail
PACK="$SHIFTLEFT_TEST_PACK_ID"
TOKEN=$(curl -sS -f -X POST "$SHIFTLEFT_URL/api/v1/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$SHIFTLEFT_EMAIL\",\"password\":\"$SHIFTLEFT_PASSWORD\"}" \
| jq -r .token)
AUTH="Authorization: Bearer $TOKEN"
STATUS_URL="$SHIFTLEFT_URL/api/v1/test-packs/$PACK/status"
# Note the pack's latest run first, so the previous run is never graded
BEFORE=$(curl -sS -f "$STATUS_URL" -H "$AUTH" | jq -r '.executionId // empty')
# Start the pack (the server answers 409 if it is already running)
curl -sS -f -X POST "$SHIFTLEFT_URL/api/v1/test-packs/$PACK/run" -H "$AUTH" > /dev/null
# Wait up to 60 minutes for a NEW run to finish
DONE=0
for i in $(seq 1 360); do
RESULT=$(curl -sS -f "$STATUS_URL" -H "$AUTH")
RUN_ID=$(echo "$RESULT" | jq -r '.executionId // empty')
STATE=$(echo "$RESULT" | jq -r '.status // empty' | tr '[:lower:]' '[:upper:]')
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "$BEFORE" ]; then
case "$STATE" in RUNNING|PENDING|QUEUED|"") ;; *) DONE=1; break ;; esac
fi
sleep 10
done
[ "$DONE" = 1 ] || { echo "Timed out waiting for a new run"; exit 1; }
echo "$RESULT" | jq .
# Reads executionId, status and successRate; if your installation's Swagger page
# names them differently, adjust the jq filters. Fails closed below 95%.
echo "$RESULT" | jq -e '(.successRate // 0) >= 95' > /dev/null
branches:
main:
- step:
name: Shift-Left API Tests (main)
script:
- apt-get update -qq && apt-get install -y -qq jq
- |
set -euo pipefail
PACK="$SHIFTLEFT_TEST_PACK_ID"
TOKEN=$(curl -sS -f -X POST "$SHIFTLEFT_URL/api/v1/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$SHIFTLEFT_EMAIL\",\"password\":\"$SHIFTLEFT_PASSWORD\"}" \
| jq -r .token)
AUTH="Authorization: Bearer $TOKEN"
STATUS_URL="$SHIFTLEFT_URL/api/v1/test-packs/$PACK/status"
# Note the pack's latest run first, so the previous run is never graded
BEFORE=$(curl -sS -f "$STATUS_URL" -H "$AUTH" | jq -r '.executionId // empty')
# Start the pack (the server answers 409 if it is already running)
curl -sS -f -X POST "$SHIFTLEFT_URL/api/v1/test-packs/$PACK/run" -H "$AUTH" > /dev/null
# Wait up to 60 minutes for a NEW run to finish
DONE=0
for i in $(seq 1 360); do
RESULT=$(curl -sS -f "$STATUS_URL" -H "$AUTH")
RUN_ID=$(echo "$RESULT" | jq -r '.executionId // empty')
STATE=$(echo "$RESULT" | jq -r '.status // empty' | tr '[:lower:]' '[:upper:]')
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "$BEFORE" ]; then
case "$STATE" in RUNNING|PENDING|QUEUED|"") ;; *) DONE=1; break ;; esac
fi
sleep 10
done
[ "$DONE" = 1 ] || { echo "Timed out waiting for a new run"; exit 1; }
echo "$RESULT" | jq .
# Reads executionId, status and successRate; if your installation's Swagger page
# names them differently, adjust the jq filters. Fails closed below 95%.
echo "$RESULT" | jq -e '(.successRate // 0) >= 95' > /dev/nullPipeline variables
Define these in Bitbucket Repository settings → Pipelines → Repository variables (per repo) or Workspace settings → Pipelines → Workspace variables (across many repos). Mark credential values as Secured to mask them in build logs.
| Variable | Description |
|---|---|
| SHIFTLEFT_URL | Base URL of your Shift-Left API deployment. Define as a Repository, Deployment, or Workspace variable depending on scope. |
| SHIFTLEFT_EMAIL / SHIFTLEFT_PASSWORD | Authentication credentials. Always mark as Secured so values are masked in logs. |
| SHIFTLEFT_TEST_PACK_ID | ID of the test pack. Repository-scoped variable typically. |
The pipe, once it is published
Not yet installableThe pipe source is public and wraps @totalshiftleft/ci — the single implementation behind the GitHub Action, the Azure DevOps task, the CircleCI orb and the GitLab component, so it grades a run identically to all of them. The image is not on Docker Hub yet, so the snippet below is what the step will look like, not something you can run today.
pipelines:
default:
- step:
name: API tests
script:
- pipe: totalshiftleft/shiftleft-test-pack:1.0.0
variables:
SHIFTLEFT_SERVER_URL: 'https://tenant.totalshiftleft.ai'
SHIFTLEFT_API_EMAIL: $SHIFTLEFT_API_EMAIL
SHIFTLEFT_API_PASSWORD: $SHIFTLEFT_API_PASSWORD
SHIFTLEFT_TEST_PACK_ID: 'pack_123'
SHIFTLEFT_PASS_THRESHOLD_PERCENT: '95'
artifacts:
- shiftleft-test-pack-summary.json
- shiftleft-test-pack-results.xmlDefine SHIFTLEFT_API_EMAIL and SHIFTLEFT_API_PASSWORD as secured repository, deployment or workspace variables so they are masked in the log. Bitbucket picks JUnit XML up automatically from a few well-known locations, so setting SHIFTLEFT_TEST_RESULTS_XML_PATH to test-results/shiftleft.xml puts the run in the Tests tab.
Gate decision codes
Printed to the step log as [ShiftLeft] Output decision=<code> and repeated in the JSON summary artifact. The step exits 0 when the gate passes and 1 when it fails, so it gates the build.
| Decision | Meaning |
|---|---|
| PASSED | The run finished and cleared every gate you configured. |
| GATE_FAIL_THRESHOLD | The run finished below your pass-rate threshold. |
| GATE_FAIL_ERROR_TESTS | At least one test finished in ERROR and fail-on-error-tests was on. |
| FAILED | The execution itself failed. |
| COMPLETED_WITH_ISSUES | The gate failed but gate-failure-result was set to warn instead. |
| OK | The run completed with no gate configured to judge it. |
| TIMEOUT | The run had not finished within timeout-minutes. |
| TRIGGER_ONLY | wait-for-completion was false, so nothing was graded. |
Step-by-step setup, every input and troubleshooting: Any CI via the REST API (help centre).
Bitbucket Pipelines integration — FAQ
Is there a first-party Shift-Left API Bitbucket pipe?
The pipe exists in source — it lives in the public Shift-Left-API-Integrations repository and wraps the same shared runner as every other Shift-Left API CI integration. Neither the pipe image nor the base runner image is on Docker Hub yet, so a pipe: totalshiftleft/shiftleft-test-pack reference will not resolve today. Until it does, the script above — calling the public REST API directly with curl and jq — is the real, working way to run test packs from Bitbucket Pipelines.Does this work with Bitbucket Data Center / self-managed Bitbucket?
The script above is written for Bitbucket Cloud Pipelines. For Bitbucket Data Center / self-managed deployments, the equivalent CI is Bamboo or third-party runners — the same public REST API pattern still applies, just wired into whatever job runner you use.How do I store credentials in Bitbucket?
Use Bitbucket repository, deployment, or workspace variables with the Secured flag enabled. Secured variables are masked in logs and not exposed to forked pull requests by default. For credentials shared across many repos, use Workspace variables.How do I gate pull requests on test results?
The example script exits non-zero when the pass rate drops below your threshold. Combine with Bitbucket's "Require successful builds" merge check (Repository settings → Branch permissions → Merge checks) to block merging on API test failures.How do I run multiple test packs in parallel?
Use parallel steps inside a Bitbucket Pipelines stage. Each parallel step is independent and runs on its own runner. Note that each parallel step consumes a build minute — Bitbucket Pipelines bills by build-minutes, so heavy parallelism trades cost for wall-clock time.Can this run on a self-hosted runner?
Yes. Bitbucket Pipelines supports self-hosted runners. Configure your runner inside the same network as your self-hosted Shift-Left API deployment, then reference the runner in the runs-on key of your pipeline step.When will the pipe be installable?
The source is written, tested and public. Publishing it means pushing the shared runner image to Docker Hub and then the pipe image on top of it; a Bitbucket pipes-catalog listing is a separate manual submission to Atlassian, though the pipe works from Docker Hub without one. We are not committing to a date. This REST API pattern will keep working either way — the pipe is a convenience wrapper around the same endpoints, not a replacement for them.
More CI/CD integrations
Add API testing to your Bitbucket pipelines
Start a 15-day trial, or use Professional or Enterprise. The REST API pattern above works today — no need to wait for the pipe.