Skip to content

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 (actionIdactionPath, virtualUserIdvirtualUserName + userProfileIndex, connectionIdconnectionName, counterIdcounterPath, regionIdregion) Every report-item filter you build, and every response row you parse, changes
2 SampleType values renamed: HIT/CONTAINERBASIC, HTTPWEB, MONITORLOAD, ERRORERRORS, 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.actionIdactionPath; TreeEntry.virtualUserId/actionIdvirtualUserName/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:

  1. POST /analysis/bench-data-migration/check/{id}
  2. if OKPOST /analysis/bench-data-migration/{id}, then poll the task id until completion (or poll GET /runtime/bench-results/{id} until migrationState is DONE and dataVersion is V2)
  3. 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: BenchErrorErrorSample

- { "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 (see GET /analysis/bench-infos/virtual-users/{benchReportId}), carrying the aggregation nodes a run produced. A client that deserialises the polymorphic Action hierarchy strictly must add it.
  • PlaywrightSpecAction is now a container: it gained a children list and no longer implements TerminalAction. 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, MonitorSampleBasicSample, 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, MetricIdSLAMonitorThreshold. 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

  1. Replace every actionId / virtualUserId / regionId / connectionId / counterId filter 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.
  2. Update hard-coded sampleType values (§2.3).
  3. Update parsing of TableEntry, TreeEntry, TopResult, ErrorSample, NumberCounterValue, TextualCounterValue, ThresholdAlarm (§5).
  4. Drop the two removed endpoints (§4).
  5. If you report on runs older than the upgrade, add the check-migrate-poll-read sequence (§3).
  6. Add AnalysisAction to your polymorphic Action deserialiser, and expect children on PlaywrightSpecAction (§5.10).
  7. Upgrade on-premise load generators along with the backend (§5.12).
  8. Optional: replace your own metric/sample-type pairing rules with allow-select + validate (§6.1).

  • 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