REST API changes: 16.2 to 17.0¶
This page lists every change to the OctoPerf REST API between 16.2.x and
17.0.0. 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.
Read the first two sections at least: the analysis data model changed, and runs recorded before 17.0 answer empty until their data has been updated.
Info
The vocabulary differs slightly between this page and the interface. What the API calls a data migration is called updating the report data in the web interface - see Updating report data.
1. TL;DR: what breaks¶
| # | Change | Impact |
|---|---|---|
| 1 | Analysis samples are identified by paths and names instead of ids (actionId → actionPath, virtualUserId → virtualUserName + userProfileIndex, connectionId → connectionName, counterId → counterPath, regionId → region) |
Every report-item filter you build, and every response row you parse, changes |
| 2 | SampleType values renamed: HIT/CONTAINER → BASIC, HTTP → WEB, MONITOR → LOAD, ERROR → ERRORS, STATS removed |
Any hard-coded sampleType in a report item payload must be updated |
| 3 | Runs recorded before 17.0 carry dataVersion: V1 and return empty report data until migrated through POST /analysis/bench-data-migration/{benchResultId} |
Historical reporting automation must migrate on demand |
| 4 | POST /analysis/bench-infos/action-ids removed |
Replaced by /analysis/bench-infos/tag-keys + /tag-values, or GET /design/paths/{virtualUserId} |
| 5 | GET /analysis/errors/{virtualUserId} removed |
Use POST /analysis/errors with an ErrorsReportItem |
| 6 | Monitoring counter values and threshold alarms replace connectionId/counterId with a tags array |
Parsers of those documents must be updated |
| 7 | TableEntry.actionId → actionPath; TreeEntry.virtualUserId/actionId → virtualUserName/userProfileIndex/actionPath |
Response parsing of POST /analysis/metrics/table and /tree |
| 8 | Load generators and backend must be upgraded together (/analysis/samples polymorphic sample names changed) |
On-premise Docker agents must be re-pulled |
Everything else in this guide is additive.
2. The analysis data model (breaking)¶
2.1 Why¶
Until 16.2, a sample was tied to the entities that produced it by their
database ids: actionId, virtualUserId, regionId, connectionId,
counterId. Those ids are meaningless outside of the project that owns them,
so a report could not span two runs of two different projects, and renaming or
re-creating an action silently orphaned its history.
From 17.0 a sample carries a set of tags, each one a key⩵value pair
(the separator is U+2A75 ⩵; it needs no escaping because values contain
query strings). The identity of an action is its path in the Virtual User
tree, segments joined with U+2AFD ⫽, e.g.
My Virtual User⫽Login⫽POST /signin.
2.2 Tag keys¶
| 16.2 field | 17.0 tag key | Notes |
|---|---|---|
actionId |
actionPath |
Path in the VU tree, ⫽-separated |
virtualUserId |
virtualUserName |
The VU's name |
| (none) | userProfileIndex |
New: which user profile of the scenario the sample comes from. setUp/tearDown thread groups are suffixed -setup / -teardown |
regionId |
region |
|
injectorId |
injectorId |
Unchanged |
connectionId (monitoring) |
connectionName |
|
counterId (monitoring) |
counterPath |
|
| (none) | lifecycle |
New: PERSISTENT or EPHEMERAL monitor connection |
type (HTTP sub-type) |
subSampleType |
Also carries Hit / Container for BASIC samples |
| (none) | order, playwrightSampleType |
New, Playwright samples only |
The wire format of a filter is unchanged. You still post
{"@type":"SingleTermFilter","field":"…","term":"…"} or
MultiTermFilter with terms; only the value of field, and the values you
filter on, change. The backend encodes field⩵term into the tags field
itself.
Before:
{ "field": "actionId", "terms": ["a3f1c2…", "b7e9…"] }
After:
{ "field": "actionPath", "terms": ["My VU⫽Login⫽POST /signin"] }
2.3 SampleType¶
| 16.2 | 17.0 | Discriminated by |
|---|---|---|
HIT |
BASIC |
subSampleType⩵Hit |
CONTAINER |
BASIC |
subSampleType⩵Container |
HTTP |
WEB |
subSampleType⩵HTTP_METHOD | HTTP_RESPONSE_CODE | HTTP_MEDIA_TYPE | HTTP_MEDIA_TYPE_THROUGHPUT |
MONITOR |
LOAD |
n/a |
ERROR |
ERRORS |
n/a |
STATS |
(removed) | Stats is no longer a sample, only an embedded value object |
NUMBER_COUNTER |
NUMBER_COUNTER |
unchanged |
TEXTUAL_COUNTER |
TEXTUAL_COUNTER |
unchanged |
THRESHOLD_ALARM |
THRESHOLD_ALARM |
unchanged |
MetricId values are unchanged.
2.4 How to translate your existing ids¶
GET /design/paths/{virtualUserId} is the mapping table:
GET /design/paths/68b1f2c9e4b0a1234567890a
{
"<actionId>": { "key": "My VU⫽Login⫽POST /signin", "value": "POST /signin" }
}
key is the path, value is the action's display name. POST /design/paths/all
takes a JSON array of Virtual User ids and returns one such map per VU, in the
same order.
Report items and SLA profiles stored on the platform are rewritten
automatically by the 17.0.0 schema migration at startup, so you do not need to
patch them. Only the filters your own code builds at call time need updating.
3. Migrating pre-17.0 run data (breaking, operational)¶
A BenchResult now carries two new read-only fields:
{ "dataVersion": "V1" | "V2", "migrationState": "DONE" | "MIGRATING" }
Runs recorded before 17.0 are V1. Report queries read the V2 format
only: every report item answers empty for a V1 run. No error is returned:
the data is simply absent.
Migration is on demand; nothing migrates the whole history in the background. The web interface does the same thing when you open an old report, through the dialog described in Updating report data.
| Verb | Path | Returns |
|---|---|---|
POST |
/analysis/bench-data-migration/check/{benchResultId} |
text/plain: OK if the run can and should be migrated, KO if it is already V2, currently MIGRATING, or unknown |
POST |
/analysis/bench-data-migration/{benchResultId} |
text/plain: the async task id, or an empty body if there was nothing to do |
GET |
/admin/bench-data-migration/migrating |
Admin only: every run currently migrating, platform-wide |
Permissions: check requires Read on the bench result, the migration itself
requires Update.
Recommended sequence for a reporting job that reads historical runs:
POST /analysis/bench-data-migration/check/{id}- if
OK→POST /analysis/bench-data-migration/{id}, then poll the task id until completion (or pollGET /runtime/bench-results/{id}untilmigrationStateisDONEanddataVersionisV2) - read the report
A second call on a run already migrating or already V2 is a no-op, so the
sequence is safe to retry.
4. Removed endpoints¶
| Verb | Path | Replacement |
|---|---|---|
POST |
/analysis/bench-infos/action-ids |
POST /analysis/bench-infos/tag-values/{type} with tagKeys=actionPath, or GET /design/paths/{virtualUserId} when you want the design-side tree |
GET |
/analysis/errors/{virtualUserId} |
POST /analysis/errors with an ErrorsReportItem filtered on virtualUserName |
5. Changed payloads on existing endpoints¶
5.1 POST /analysis/metrics/table: TableEntry¶
- { "actionId": "a3f1c2…", "values": [ … ] }
+ { "actionPath": "My VU⫽Login⫽POST /signin", "values": [ … ] }
5.2 POST /analysis/metrics/tree: TreeEntry¶
- { "virtualUserId": "68b1…", "actionId": "a3f1…", "values": [ … ] }
+ { "virtualUserName": "My VU", "userProfileIndex": "0", "actionPath": "My VU⫽Login⫽POST /signin", "values": [ … ] }
5.3 POST /analysis/metrics/top: TopResult¶
Structure unchanged; the map keys are now action paths instead of action ids.
5.4 POST /analysis/errors: BenchError → ErrorSample¶
- { "benchResultId": "…", "regionId": "…", "injectorId": "…",
- "virtualUserId": "…", "actionId": "…",
- "timestamp": 0, "errorMessage": "…", … }
+ { "benchResultId": "…",
+ "tags": ["actionPath⩵My VU⫽Login⫽POST /signin", "virtualUserName⩵My VU",
+ "userProfileIndex⩵0", "region⩵eu-west-1", "injectorId⩵…"],
+ "timestamp": 0, "errorMessage": "…", … }
The same rename applies to the multipart ingestion endpoint
POST /analysis/errors/index/{benchResultId} (internal, load-generator side).
5.5 GET /analysis/errors/request|response/{benchResultId}/{actionId}/{timestamp}¶
The path is unchanged, but the third segment now holds a base64url-encoded action path where it used to hold a base64url-encoded action id. Stored request/response bodies of migrated runs are re-keyed by the data migration of section 3.
5.6 Monitoring counter values: NumberCounterValue, TextualCounterValue¶
- { "connectionId": "…", "counterId": "…", "benchResultId": "…", "timestamp": 0, "value": 42 }
+ { "tags": ["connectionName⩵prod-db-01", "counterPath⩵CPU⫽User", "lifecycle⩵PERSISTENT"],
+ "benchResultId": "…", "timestamp": 0, "value": 42 }
5.7 ThresholdAlarm¶
- { "connectionId": "…", "counterId": "…", … }
+ { "tags": ["connectionName⩵prod-db-01", "counterPath⩵CPU⫽User"], … }
tags is nullable on input and defaults to an empty set.
5.8 MonitorConnection, new field¶
{ "lifecycle": "PERSISTENT" | "EPHEMERAL" }
Nullable on input, defaults to PERSISTENT. EPHEMERAL marks a connection
created for a single run (typically an on-the-fly monitor); report templates
drop filters that name an ephemeral connection when replayed on another run,
instead of returning nothing.
5.9 BenchResult, new fields¶
dataVersion and migrationState, see section 3. Both nullable on input and
default to V1 / DONE.
5.10 Virtual User action tree: new and changed action types¶
- New action type
AnalysisAction("@class": "AnalysisAction"): a named container injected into the analysis view of a Virtual User (seeGET /analysis/bench-infos/virtual-users/{benchReportId}), carrying the aggregation nodes a run produced. A client that deserialises the polymorphicActionhierarchy strictly must add it. PlaywrightSpecActionis now a container: it gained achildrenlist and no longer implementsTerminalAction. Playwright specs report a tree (spec → group → action / network) instead of a single leaf.POST /design/imports/playwright/{projectId}now rejects an upload with no spec file (400) instead of creating an empty Virtual User.
5.11 POST /analysis/jtl/{projectId}/task¶
The scenario created to hold the import is now named after the uploaded file
(extension stripped) instead of the generic JTL import. The report returned
is therefore attached to a recognisable scenario name. No contract change.
5.12 Internal load-generator endpoint POST /analysis/samples¶
The polymorphic sample names changed: ContainerSample, HitSample,
HttpSample, MonitorSample → BasicSample, WebSample, LoadSample.
This endpoint is not part of the public API, but it means a 16.2 load
generator cannot report to a 17.0 backend: on-premise Docker agents and
Kubernetes injectors must be upgraded with the backend.
6. New endpoints¶
6.1 Report-item editing and tag discovery¶
| Verb | Path | Purpose |
|---|---|---|
POST |
/analysis/bench-infos/tag-keys/{type} |
Body: array of bench result ids. Returns the tag keys available for that SampleType |
POST |
/analysis/bench-infos/tag-values/{type} |
Query: tagKeys (repeatable). Body: { "benchResultIds": [...], "filters": [...] }. Returns tag values grouped by key, scoped by the filters already selected |
POST |
/analysis/bench-infos/allow-select/{benchReportId} |
Body: a BenchReportItem. Returns a ReportItemAllowSelect descriptor: selectable sample types, selectable metric ids per sample type, per-tag-key constraints (required, single, allowedValues, dependsOn) |
POST |
/analysis/bench-infos/validate/{benchReportId} |
Body: a BenchReportItem. Returns { "valid": bool, "violations": [ { "message", "metricId", "field" } ] } |
GET |
/analysis/bench-infos/virtual-users/{benchReportId} |
The report's Virtual Users, actualised with the analysis actions the run produced. Supports ETag / If-None-Match and answers 304 |
{type} is a SampleType name (BASIC, WEB, LOAD, ERRORS,
NUMBER_COUNTER, TEXTUAL_COUNTER, THRESHOLD_ALARM).
Use allow-select + validate rather than hard-coding which metric goes with
which sample type: the pairing is now resolved server-side and depends on the
report (a trend report over several runs does not allow the same things as a
single-run report).
6.2 SLA¶
| Verb | Path | Purpose |
|---|---|---|
GET |
/design/sla-profiles/threshold-defaults |
The whole default-threshold table, MetricId → SLAMonitorThreshold. A constant of the running version: fetch once, look up locally. Metrics without meaningful defaults are absent |
GET |
/design/sla-profiles/{id} |
Already served by the CRUD controller in 16.2, now declared in the client contract |
Authentication is enough for threshold-defaults: no project permission is
required, the table is scoped to nothing.
6.3 Commercial-tool imports¶
| Verb | Path | Purpose |
|---|---|---|
POST |
/design/imports/neoload/{projectId} |
Create Virtual Users (and scenarios) from a NeoLoad project archive |
POST |
/design/imports/neoload/{projectId}/assessment |
Survey the archive without importing anything |
POST |
/design/imports/loadrunner/{projectId} |
Create Virtual Users (and scenarios) from a LoadRunner script folder |
POST |
/design/imports/loadrunner/{projectId}/assessment |
Survey the archive without importing anything |
multipart/form-data, part name file. Optional query parameters
providerId and location: without a providerId the scenarios are skipped
and only the Virtual Users are created (a user profile has to say where it
runs). Both endpoints accept an ephemeral token.
The import response carries virtualUsers, scenarios, the assessment (the
survey is a by-product of parsing, returned so you need not upload twice) and
unconverted, the elements left for manual completion, each naming a marker
planted in the created action trees. The assessment response carries
projectName, the source product version, a portability verdict, an
inventory, a per-script/per-VU summary, the protocols or plugins used, and
the same unconverted list without markers.
For LoadRunner, upload the whole script folder, not its Action.c: an
HTML-level recording resolves only through its data/ snapshots, and a pasted
source file imports as a single request against the entry point.
6.4 Workspaces¶
| Verb | Path | Purpose |
|---|---|---|
GET |
/workspaces/members/by-authenticated-user |
The caller's enabled memberships across every workspace. Revoked memberships are never disclosed. Authentication is the whole check |
PUT /workspaces/{id}, GET /workspaces/{id}, PUT /workspaces/members/{id}
were already served by the CRUD controllers in 16.2 and are now declared in
the client contract.
6.5 Scenario run¶
POST /runtime/scenarios/run/{id}?templateId=… already existed server-side in
16.2; only the typed client contract was missing. A null templateId is
dropped from the query and the run gets the default report.
7. Migration checklist¶
- Replace every
actionId/virtualUserId/regionId/connectionId/counterIdfilter field with its 17.0 tag key (§2.2), and every filter value with the corresponding path or name.GET /design/paths/{vuId}gives you the mapping. - Update hard-coded
sampleTypevalues (§2.3). - Update parsing of
TableEntry,TreeEntry,TopResult,ErrorSample,NumberCounterValue,TextualCounterValue,ThresholdAlarm(§5). - Drop the two removed endpoints (§4).
- If you report on runs older than the upgrade, add the check-migrate-poll-read sequence (§3).
- Add
AnalysisActionto your polymorphicActiondeserialiser, and expectchildrenonPlaywrightSpecAction(§5.10). - Upgrade on-premise load generators along with the backend (§5.12).
- Optional: replace your own metric/sample-type pairing rules with
allow-select+validate(§6.1).
8. Related documentation¶
- Release Notes - the full list of changes shipped in
17.0.0 - Updating report data - the same data update, seen from the web interface
- 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 to
17.0.0