Skip to main content

CI/CD

Written by Jihed Othmani
Updated over 3 weeks ago

Overview

The Thunders CI API lets you queue test executions, poll their status, and retrieve structured reports (JUnit XML or JSON) — all through 3 endpoints under /api/ci/.

API Endpoints

POST  https://api.thunders.ai/api/ci/run 
GET https://api.thunders.ai/api/ci/run/{runId}
GET https://api.thunders.ai/api/ci/run/{runId}/report

Authentication

The API requires authentication using a Bearer token:

Authorization: Bearer YOUR_THUNDER_TEST_TOKEN

You must obtain a valid API token from the Thunders platform. This token should be stored securely as a secret in your environment or GitHub repository.

Headers

Header

Value

Description

Authorization

Bearer YOUR_THUNDER_TEST_TOKEN

Authentication token

X-MS-API-ROLE

M2M

Indicates a machine-to-machine API call

Content-Type

application/json

Specifies the request body format

POST /api/ci/run — Queue Test Runs

Queues test executions for one or more test cases and/or test sets. All runs are grouped under a shared runId. Returns URLs you can use to poll status and fetch the final report.

Request Body

At least one of testCaseIds or testSetIds must be provided. Both can be provided in the same request and in that case both will be run.

{
"projectId": "c8e34ec4-2464-43c7-8db8-1b3a47a22337",
"testCaseIds": [
"a836fadc-377a-46fe-96b0-21f37c626bf9",
"61015c47-612f-47fa-82a6-41d910832c1b"
],
"testSetIds": [
"8eea2fd3-da6d-4434-a310-176be84c5646"
],
"environmentId": "eca24252-e566-40a8-b2b0-707b7efa85d8",
"personaId": "70d0ac52-fe94-4d89-aba9-8ef28dd8c04c",
"maxParallelism": 5,
"maxRetries": 1,
"browserSettings": {
"location": "Paris",
"resolution": "1440x900",
"deviceType": "Desktop"
}
}

Parameter

Type

Required

Default

Description

projectId

string (UUID)

Yes

-

The unique identifier of the project.

testCaseIds

array (UUIDs)

No

-

List of test case IDs to run.

testSetIds

array (UUIDs)

No

-

List of test set IDs. Each set expands to its test cases.

environmentId

string (UUID)

Yes

-

The environment to use.

personaId

string (UUID)

Yes

-

The persona for test execution.

maxParallelism

int

No

4

Max parallel tests. 0 = maximum.

maxRetries

int

No

0

Retry attempts for failed tests (0-3).

testAssetReferences

array

No

-

Test asset refs for batch execution.

browserSettings.location

string

No

"Paris"

Geo cluster: Paris, London, Amsterdam, SanFrancisco, NewYork.

browserSettings.browserType

string

No

"Chromium"

Chromium, Chrome, Firefox, or Safari.

browserSettings.deviceType

string

No

"Desktop"

Desktop, Mobile, or Tablet.

browserSettings.resolution

string

No

"1280x720"

Viewport resolution (e.g., "1440x900").

browserSettings.javascript

boolean

No

true

Enable/disable JavaScript.

browserSettings.highlightElements

boolean

No

true

Highlight target elements.

browserSettings.ignoreHttpsErrors

boolean

No

false

Ignore HTTPS errors.

browserSettings.darkTheme

boolean

No

false

Dark theme mode.

browserSettings.avoidDetection

boolean

No

false

Bot detection avoidance.

browserSettings.locale

string

No

-

Browser locale (e.g., "en-US").

browserSettings.proxy

string

No

-

Proxy server configuration.

browserSettings.headers

dictionary

No

-

Custom HTTP headers.

Response — 202 Accepted

{
"runId": "01966a3e-...",
"statusUrl": "/api/ci/run/01966a3e-...",
"reportUrl": "/api/ci/run/01966a3e-.../report"
}

Field

Type

Description

runId

string (UUID)

Unique identifier for the CI run.

statusUrl

string

Relative URL to poll the run's status.

reportUrl

string

Relative URL to fetch the final report.

GET /api/ci/run/{runId} — Poll Run Status

Returns aggregated status of all test runs. Poll until status is terminal (passed, failed, stopped, cancelled).

Response — 200 OK

{
"runId": "01966a3e-...",
"status": "running",
"testCount": 5,
"passedCount": 2,
"failedCount": 0,
"runningCount": 2,
"queuedCount": 1,
"startedAt": "2026-03-20T14:30:00Z",
"endedAt": null,
"durationMs": null
}

Field

Type

Description

runId

string (UUID)

The CI run identifier.

status

string

queued, running, stopping, stopped, cancelled, failed, passed.

testCount

int

Total test runs.

passedCount

int

Tests passed.

failedCount

int

Tests failed.

runningCount

int

Tests running.

queuedCount

int

Tests queued.

startedAt

datetime?

When earliest test started.

endedAt

datetime?

When last test ended.

durationMs

long?

Wall-clock duration in ms.

GET /api/ci/run/{runId}/report — Fetch Report

Returns the test execution report. Format depends on the Accept header:

  • Default (or application/xml): JUnit XML — compatible with Jenkins, GitHub Actions (dorny/test-reporter), GitLab, etc.

  • Accept: application/json: JSON report with structured data.

JSON Response Example

{
"tests": 3,
"failures": 1,
"skipped": 0,
"time": 45.2,
"testSuites": [
{
"name": "My Test Set",
"tests": 2,
"failures": 1,
"skipped": 0,
"time": 30.1,
"timestamp": "2026-03-20T14:30:00Z",
"testCases": [
{
"name": "Login flow",
"className": "My Test Set",
"time": 15.0,
"status": "passed",
"failure": null
},
{
"name": "Checkout flow",
"className": "My Test Set",
"time": 15.1,
"status": "failed",
"failure": {
"message": "Element not found",
"type": "AssertionFailure",
"body": "Step 3: Click Add to cart — element not found."
}
}
]
}
]
}

Field

Type

Description

tests

int

Total test cases across all suites.

failures

int

Total failed test cases.

skipped

int

Total skipped test cases.

time

double

Total execution time in seconds.

testSuites

array

List of test suite reports.

testSuites[].testCases[].failure

object?

message, type, body. null if passed.

Error Handling

The API uses standard HTTP status codes:

  • 202 Accepted: POST /api/ci/run — tests were successfully queued

  • 200 OK: GET endpoints — request was successful

  • 400 Bad Request: Malformed request or missing required parameters

  • 401 Unauthorized: Authentication failed or token is invalid

  • 403 Forbidden: Insufficient permissions

  • 404 Not Found: No test runs found for the given runId

  • 500 Internal Server Error: Unexpected server error

Using with GitHub Actions

Integrate the Thunders CI API with GitHub Actions to automate test execution in your CI/CD pipeline.

Example GitHub Action Workflow

name: Run Thunders Tests

on:
workflow_dispatch:

permissions:
contents: read
checks: write

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Queue Thunders CI Run
id: trigger
run: |
set -euo pipefail
TMPFILE=$(mktemp)
curl --no-buffer -s -X POST https://api.thunders.ai/api/ci/run \
-H "Authorization: Bearer $\{{ secrets.THUNDER_TEST_TOKEN }}" \
-H "X-MS-API-ROLE: M2M" \
-H "Content-Type: application/json" \
-d '{
"projectId": "YOUR_PROJECT_ID",
"testSetIds": ["YOUR_TEST_SET_ID"],
"environmentId": "YOUR_ENVIRONMENT_ID",
"personaId": "YOUR_PERSONA_ID",
"maxParallelism": 5,
"maxRetries": 1,
"browserSettings": {
"location": "Paris",
"resolution": "1920x1080",
"deviceType": "Desktop"
}
}' \
-w '\n%{http_code}' 2>&1 | tee "$TMPFILE"
HTTP_CODE=$(tail -1 "$TMPFILE")
BODY=$(head -n -1 "$TMPFILE")
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "Failed (HTTP $HTTP_CODE): $BODY"; exit 1
fi
echo "status_url=$(echo $BODY | jq -r '.statusUrl')" >> $GITHUB_OUTPUT
echo "report_url=$(echo $BODY | jq -r '.reportUrl')" >> $GITHUB_OUTPUT

- name: Wait for completion
id: run-tests
run: |
set -euo pipefail
MAX_WAIT=5400; INTERVAL=30; ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
RESP=$(curl -sS --fail-with-body \
"https://api.thunders.ai$\{{ steps.trigger.outputs.status_url }}" \
-H "Authorization: Bearer $\{{ secrets.THUNDER_TEST_TOKEN }}" \
-H "X-MS-API-ROLE: M2M")
STATUS=$(echo "$RESP" | jq -r '.status')
echo "[$ELAPSED s] status=$STATUS"
case "$STATUS" in
passed|failed|stopped|cancelled)
echo "final_status=$STATUS" >> $GITHUB_OUTPUT; break;;
esac
sleep $INTERVAL; ELAPSED=$((ELAPSED+INTERVAL))
done
[ $ELAPSED -ge $MAX_WAIT ] && echo "Timed out" && exit 1

- name: Download JUnit report
if: always() && steps.run-tests.conclusion == 'success'
run: |
curl -sS --fail-with-body \
"https://api.thunders.ai$\{{ steps.trigger.outputs.report_url }}" \
-H "Authorization: Bearer $\{{ secrets.THUNDER_TEST_TOKEN }}" \
-H "X-MS-API-ROLE: M2M" \
-o test-results.xml

- name: Publish test results
uses: dorny/test-reporter@v1
if: always() && steps.run-tests.conclusion == 'success'
continue-on-error: true
with:
name: Thunders CI Results
path: test-results.xml
reporter: java-junit

- name: Fail if tests did not pass
if: steps.run-tests.outputs.final_status != 'passed'
run: exit 1

Setting Up GitHub Secrets

  1. Go to your GitHub repository

  2. Navigate to Settings > Secrets and variables > Actions

  3. Click "New repository secret"

  4. Name: THUNDER_TEST_TOKEN

  5. Value: Your Thunders API token

  6. Click "Add secret"

Customizing the Workflow

You can customize the workflow by:

  1. Changing the trigger events (e.g., on push, on schedule)

  2. Modifying test parameters (projectId, testCaseIds, testSetIds, environmentId, personaId)

  3. Adjusting the polling interval (INTERVAL) and timeout (MAX_WAIT)

  4. Adding additional steps to process test results

Test Asset Batch

The Test Asset Batch feature allows you to run the same test case multiple times with different data variations by uploading CSV files as test assets. Each row in the CSV becomes a separate test run, enabling data-driven testing at scale.

How It Works

  1. Upload CSV files as test assets to your project (via the Thunders web app or API)

  2. Reference the CSV files in your API request using the testAssetReferences parameter

  3. Each CSV row generates a separate test run with its own variable overrides and browser settings

  4. All runs from the same batch are grouped under a shared batchId

The testAssetReferences Parameter

Add the testAssetReferences field to your request body:

{
"projectId": "your-project-id",
"testCaseIds": ["your-test-case-id"],
"environmentId": "your-environment-id",
"personaId": "your-persona-id",
"testAssetReferences": ["FILE_MYDATA_CSV", "FILE_USERS_CSV"]
}

Reference format: When you upload a file named mydata.csv, its reference becomes FILE_MYDATA_CSV. The naming convention is FILE__ where the filename is uppercased, and special characters are replaced with underscores.

References can also be wrapped in brackets: [FILE_MYDATA_CSV].

To know more about the batching feature check out this documentation

Combining Multiple CSV Files

You can pass multiple CSV references in the testAssetReferences array. Rows from all files are merged sequentially into a single batch with continuous indexing.

"testAssetReferences": ["FILE_BROWSERS_CSV", "FILE_MOBILE_CSV"]

If browsers.csv has 3 rows and mobile.csv has 2 rows, the batch will contain 5 total runs indexed 1 through 5.

Did this answer your question?