Skip to content

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.userProfileIndexTreeEntry.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 userProfileIndex filter 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

  1. Parse phase instead of userProfileIndex on the rows of POST /analysis/metrics/tree, and group them on virtualUserName + phase (§2).
  2. Move any per-profile filtering of the Results Tree from your code into a userProfileIndex filter on the report item (§3).
  3. Match a Virtual User of GET /analysis/bench-infos/virtual-users/… by name, not by root id, and expect one root per name (§4.1).
  4. 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).
  5. Echo the ETag verbatim, W/ prefix included (§4.3).
  6. If you called the undocumented JTL staging endpoint, switch to POST /analysis/jtl/{projectId}/import (§5).