REST API changes: 17.0.0 to 17.0.1¶
This page lists every change to the OctoPerf REST API between 17.0.0 and
17.0.1. It is written for the people who call the API directly - CI/CD
pipelines, custom reporting jobs, in-house dashboards. If you only use the web
interface or the MCP server, you have nothing to do: both were
updated with the platform.
If you are upgrading from 16.2 or earlier, read
REST API changes: 16.2 to 17.0 first, then this page.
One change breaks an existing response: the rows returned by the Results
Tree endpoint no longer carry userProfileIndex, they carry phase. Everything
else is either additive or a payload that got smaller.
1. TL;DR: what changes¶
| # | Change | Impact |
|---|---|---|
| 1 | TreeEntry.userProfileIndex → TreeEntry.phase on POST /analysis/metrics/tree |
Breaking: response parsing, and how you group its rows |
| 2 | The Results Tree report item now accepts a userProfileIndex filter |
Additive: this is how you scope a tree to one user profile |
| 3 | GET /analysis/bench-infos/virtual-users/{benchReportId} returns one Virtual User per name |
Fewer roots than before, and their ids are no longer a profile |
| 4 | The same endpoint drops the virtualUserName tag from action nodes, and trims the design action they point to |
Much smaller payload; read the name from the root |
| 5 | The same endpoint now returns a weak ETag (W/"…") |
Send it back verbatim in If-None-Match |
| 6 | POST /analysis/jtl/{projectId}/task replaced by POST /analysis/jtl/{projectId}/import |
The old path was undocumented; the new one is public |
2. POST /analysis/metrics/tree: TreeEntry (breaking)¶
A row of the Results Tree used to name the user profile it came from by its index, with a suffix when the measurement came from a Set Up or Tear Down phase. The index is gone from the row; what remains is the phase, as a value of its own:
- { "virtualUserName": "My VU", "userProfileIndex": "0-setup", "actionPath": "My VU⫽Login", "values": [ … ] }
+ { "virtualUserName": "My VU", "phase": "SETUP", "actionPath": "My VU⫽Login", "values": [ … ] }
phase is one of MAIN, SETUP, TEARDOWN.
Why. A Results Tree row is now keyed on the Virtual User and its phase rather than on the user profile, so the same Virtual User played by ten user profiles produces one set of rows instead of ten. The user profile did not disappear from the data - it moved from the response to the request, where it belongs:
- to see every profile at once, which is the default, read the rows as they come. Aggregating them yourself is no longer needed;
- to scope the tree to one user profile, add a
userProfileIndexfilter to the report item (see below) rather than filtering the response.
If your code grouped rows by userProfileIndex, group them by
virtualUserName + phase instead. If it filtered rows on a specific index,
move that filter into the request.
3. userProfileIndex on a Results Tree report item¶
A StatisticTreeReportItem now accepts the userProfileIndex tag key, which
it previously refused. Nothing else about the filter changed - same wire
format, same values (the profile index, suffixed -setup or -teardown for
the Set Up and Tear Down phases):
{ "@type": "SingleTermFilter", "field": "userProfileIndex", "term": "0-setup" }
POST /analysis/bench-infos/allow-select/{benchReportId} reports this, so an
integration that reads the descriptor rather than hard-coding the keys picks it
up on its own.
4. GET /analysis/bench-infos/virtual-users/{benchReportId}¶
This endpoint returns the Virtual Users of a report, actualised with the actions the run produced. Three things changed, all of them to keep a report of a wide run readable: on a large production report the response went from about 150 MB to about 65 MB of JSON, and the same report now opens in seconds where it used to freeze a browser for minutes.
4.1 One Virtual User per name¶
A result project holds one copy of a Virtual User per user profile that plays it. The response used to carry all of them; it now carries one root per Virtual User name.
Two consequences for a caller:
- there are fewer roots than there are user profiles, and their number no longer tells you how many profiles the scenario ran;
- the id of a root is no longer the id of a given profile's copy. Match a
root by its
name, which is what the samples carry. Resolving a Virtual User by root id against a user profile no longer works.
4.2 Action nodes carry fewer tags, and a trimmed source¶
An action node used to carry a virtualUserName tag for every Virtual User
whose samples take the same action path - on a report where a hundred Virtual
Users share a login sequence, that was the bulk of the payload. Action nodes now
carry actionPath only, plus order and playwrightSampleType where they
apply.
Read the Virtual User name from the root of the tree, not from a node.
The design action a node points back to (source) is also trimmed. For an HTTP
request it keeps what draws the node - method, path, query parameters and
server - and drops the rest: headers, body, content encoding, resources filter
and think time. Any other action type keeps its own fields, minus its children,
which the tree already carries.
If you were reading a request's headers or body out of this response, read the
Virtual User itself instead, through
POST /design/virtual-users/by-projects or GET /design/virtual-users/{id}.
4.3 The ETag is now weak¶
The response ETag is now weak, i.e. prefixed with W/:
ETag: W/"7d3f1a9c"
Send the value back verbatim, W/ included, in If-None-Match to get a
304. A client that strips the prefix, or that rebuilds the header from the
bare value, gets a full response every time.
5. JTL import: one endpoint instead of two¶
POST /analysis/jtl/{projectId}/task is removed. It was hidden from the
public OpenAPI description and only the web interface called it, so no
documented contract breaks - but if you had found it, this is its replacement:
POST /analysis/jtl/{projectId}/import
Content-Type: multipart/form-data
one part named `file` - a zip of CSV JTL files
→ 200 { "taskId": "…", "benchResultId": "…", "reportName": "…" }
Permission is unchanged (Project / Create). reportName is the name of the
uploaded archive without its extension, sanitised by the server; it is the name
of both the scenario and the report.
The old flow took two calls - one to stage the archive, one to start the import
- and left a result project, a scenario and a bench result behind whenever the
second call never came. The new endpoint stores and launches in the same
call, so poll taskId the usual way and read
JtlImportTaskResult.benchReportId from the finished task:
curl -X POST "https://api.octoperf.com/analysis/jtl/${PROJECT_ID}/import" \
-H "Authorization: Bearer ${TOKEN}" \
-F "file=@results.zip"
An agent can now do the same thing in one tool call - see the MCP server.
6. Migration checklist¶
- Parse
phaseinstead ofuserProfileIndexon the rows ofPOST /analysis/metrics/tree, and group them onvirtualUserName+phase(§2). - Move any per-profile filtering of the Results Tree from your code into a
userProfileIndexfilter on the report item (§3). - Match a Virtual User of
GET /analysis/bench-infos/virtual-users/…byname, not by root id, and expect one root per name (§4.1). - Read the Virtual User name from the root rather than from an action node's tags, and read request headers or bodies from the design endpoints rather than from this response (§4.2).
- Echo the
ETagverbatim,W/prefix included (§4.3). - If you called the undocumented JTL staging endpoint, switch to
POST /analysis/jtl/{projectId}/import(§5).
7. Related documentation¶
- Release Notes - the full list of changes shipped in
17.0.1 - REST API changes: 16.2 to 17.0 - read this first if you are upgrading from
16.2or earlier - Rest API - how to authenticate and start a test from your pipeline
- MCP Server - drive OctoPerf from an AI agent instead of calling the API yourself
- Upgrading Version - upgrading a self-hosted installation