The canonical references for Traffic Parrot's programmatic-setup REST APIs are:
The rest of this page works through one concrete example: a CI/CD job that toggles a scenario, adds it to the Composite, asserts that the system under test called the expected endpoints, and cleans up after itself. Treat it as an illustration of how to use the APIs above — the full API surface is in the references.
The walkthrough below drives a running Traffic Parrot instance from the command line and from Java code. You will not click a single button in the UI — everything happens through HTTP requests, which is what you need when Traffic Parrot is part of a CI/CD pipeline.
Specifically, the example covers:
Each step shows two flavours of the same call: a one-line curl
invocation suitable for a bash step in any CI runner, and an equivalent
Java 11+ HttpClient
snippet for engineers who prefer to drive Traffic Parrot from JUnit setup methods
or build-tool plugins.
To follow this chapter you should first:
8080 is assumed throughout the examples
— adjust if you have configured a different port.curl available on the command line for the shell examples.HttpClient examples.
The standard library java.net.http.HttpClient is used — no
third-party HTTP library is needed.All examples in this chapter target Traffic Parrot 5.59.x.
A scenario in Traffic Parrot is either enabled (its mappings are being served on the configured port) or disabled. Toggling that flag is one of the most common pipeline operations — you typically enable a fixture scenario at the start of a job and disable it at the end so that subsequent jobs start from a clean baseline.
Use PUT /api/scenarios/{name} with a JSON body that names the new
state. The body fields that are omitted keep their previous values, so you can
send a minimal update.
$ curl -X PUT http://localhost:8080/api/scenarios/checkoutHappyPath \
-H "Content-Type: application/vnd.trafficparrot.scenarios.v1+json" \
-H "Accept: application/vnd.trafficparrot.scenarios.v1+json" \
-d '{"name":"checkoutHappyPath","enabled":true}'
The versioned application/vnd.trafficparrot.scenarios.v1+json
media type is preferred over plain application/json — it
pins the request to the v1 contract so future API revisions cannot silently
change the shape of the response your pipeline parses.
On success the response is 200 OK with the full updated scenario
as JSON. On a port conflict the response is 412 Precondition Failed
with a plain-text body explaining which port is busy.
HttpClientHttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/scenarios/checkoutHappyPath"))
.header("Content-Type", "application/vnd.trafficparrot.scenarios.v1+json")
.header("Accept", "application/vnd.trafficparrot.scenarios.v1+json")
.PUT(HttpRequest.BodyPublishers.ofString(
"{\"name\":\"checkoutHappyPath\",\"enabled\":true}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException("Scenario toggle failed: " + response.statusCode() + " " + response.body());
}
The Composite Scenario aggregates the mappings of several sub-scenarios on a single shared port. This is useful when one virtual service needs to behave like several upstream services at once, or when an end-to-end test depends on a combination of scenarios being active together.
Use POST /api/scenarios/http/composite/add with a
scenario query parameter. The request body is empty.
$ curl -X POST "http://localhost:8080/api/scenarios/http/composite/add?scenario=checkoutHappyPath" \
-H "Accept: application/json"
On success the response is 200 OK with a small confirmation message:
{"message": "Scenario 'checkoutHappyPath' added to Composite"}
If the named scenario does not exist the response is 400 Bad Request
with a JSON error field. Treat anything outside the 2xx range as
a build failure in your pipeline.
HttpClientHttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/scenarios/http/composite/add?scenario=checkoutHappyPath"))
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException("Compose-add failed: " + response.statusCode() + " " + response.body());
}
The matching POST /api/scenarios/http/composite/remove?scenario=...
endpoint takes a scenario back out of the composite. You will typically pair the
two around each test job.
After the system under test has finished its run, you usually want to assert
that it actually called the endpoints you mocked. GET /api/http/requests
returns the journal of every HTTP request Traffic Parrot has received, with
method, URL, headers, body, and whether the request matched a stubbed mapping.
$ curl http://localhost:8080/api/http/requests \
-H "Accept: application/json"
The response shape is roughly:
{
"requestJournalDisabled": false,
"meta": { "total": 2 },
"requests": [
{ "request": { "url": "/api/checkout", "method": "POST" }, "wasMatched": true, "...": "..." },
{ "request": { "url": "/api/checkout", "method": "GET" }, "wasMatched": true, "...": "..." }
]
}
Note the nesting: each entry has the inbound request fields
(url, method, headers, body)
under a request sub-object, while wasMatched sits at
the top level alongside the entry's responseDefinition and timing.
Loop over requests in your pipeline and assert what you need.
DELETE /api/http/requests clears the journal — useful as a
per-test or per-job reset.
HttpClientHttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/http/requests"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// response.body() is the JSON above; parse with your preferred library and
// assert that the expected endpoints appear with wasMatched == true.
Before kicking off a test job it is worth checking that the fixture scenarios
your tests expect are actually loaded — otherwise the failure later in
the run is harder to diagnose. GET /api/scenarios returns every
scenario currently known to Traffic Parrot, including ports and enabled state.
$ curl http://localhost:8080/api/scenarios \
-H "Accept: application/vnd.trafficparrot.scenarios.v1+json"
Sample response (abridged):
[
{ "name": "Composite", "enabled": true, "httpPort": 8080, "httpsPort": null },
{ "name": "checkoutHappyPath", "enabled": false, "httpPort": 8090, "httpsPort": null },
{ "name": "checkoutCardDecline","enabled": false, "httpPort": 8091, "httpsPort": null }
]
HttpClientHttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/scenarios"))
.header("Accept", "application/vnd.trafficparrot.scenarios.v1+json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Parse the array and assert the names you depend on are present.
A typical job that drives Traffic Parrot for a single integration test step looks like this. The example is shell so it slots into GitHub Actions, GitLab CI, Jenkins, or any other runner without translation.
#!/usr/bin/env bash
set -euo pipefail
TP_URL="${TP_URL:-http://localhost:8080}"
# 1. Sanity check: required fixture scenarios are loaded.
curl --fail --silent "$TP_URL/api/scenarios" \
-H "Accept: application/vnd.trafficparrot.scenarios.v1+json" \
| grep -q '"checkoutHappyPath"'
# 2. Enable the fixture and add it to the composite for this job.
curl --fail --silent -X PUT "$TP_URL/api/scenarios/checkoutHappyPath" \
-H "Content-Type: application/vnd.trafficparrot.scenarios.v1+json" \
-d '{"name":"checkoutHappyPath","enabled":true}' > /dev/null
curl --fail --silent -X POST "$TP_URL/api/scenarios/http/composite/add?scenario=checkoutHappyPath" > /dev/null
# 3. Run the system under test (omitted) — it should call into Traffic Parrot.
./gradlew :acceptance:test
# 4. Assert that the expected requests were received.
curl --fail --silent "$TP_URL/api/http/requests" | grep -q '"/api/checkout"'
# 5. Teardown: take the scenario out of the composite and clear the journal.
curl --fail --silent -X POST "$TP_URL/api/scenarios/http/composite/remove?scenario=checkoutHappyPath" > /dev/null
curl --fail --silent -X DELETE "$TP_URL/api/http/requests" > /dev/null
Three things make this template robust enough to copy into a real pipeline:
set -euo pipefail so any failing curl aborts the job;
--fail on each curl so a non-2xx response is surfaced as a non-zero
exit code; and a teardown step that runs whether the test passed or failed
(move it into a trap if your runner does not have an explicit
always-run hook).
415 Unsupported Media Type on
PUT /api/scenarios/{name}.
Fix: add the Content-Type:
application/vnd.trafficparrot.scenarios.v1+json header (or plain
application/json) on the request — Traffic Parrot rejects
the request when the body media type is not declared.404 Not Found on a scenario name you
are sure exists. Fix: scenario names are case-sensitive and
URL-encoded. Check the spelling against the output of
GET /api/scenarios.412 Precondition Failed on
PUT /api/scenarios/{name} with a port-in-use message.
Fix: another scenario is already listening on the requested
port. Disable it first or pick a different port.connection refused from curl.
Fix: confirm the Traffic Parrot management port. The
default is 8080 — if you set
trafficparrot.gui.http.port to a different value in
trafficparrot.properties, use that.GET /api/http/requests returns an
empty requests array even though tests ran. Fix:
check that requestJournalDisabled is false in the
response — the request journal is disabled by default in some
performance-tuning profiles. Re-enable it in the scenario settings.