Problem
I usually prefer the admin ui for this stuff, but the Java script can’t handle the TIME RANGE and FLOW VERSION constraints when hitting the CXone analytics endpoint.
Code
Map<String, Object> payload = new HashMap<>();
payload.put("flowId", "f_99281");
payload.put("timeRange", "P14D");
payload.put("metrics", List.of("nodeTraversal", "errorDistribution", "dropOffRate"));
HttpResponse<String> res = HttpClient.newHttpClient().send(HttpRequest.newBuilder().uri(URI.create("https://api.nicecxone.com/v1/analytics/flows/execution")).header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(new ObjectMapper().writeValueAsString(payload))).build(), HttpResponse.BodyHandlers.ofString());
Error
Throws a 400 Bad Request with InvalidPaginationOffset past the OFFSET LIMITS.
Question
Need the exact offset structure to bypass the cap so the bottleneck detection algorithm runs and syncs with our CI/CD dashboard.
{“error”:“invalid_token”,“error_description”:“Access token expired.”} pops up halfway through that query if you don’t refresh the bearer token before the job actually spins up. You’ll get a 401 because the API rejects the request once the token hits that two-hour mark, even if the pagination params look correct. You need to call oauthApi.postOAuthApiToken with refresh_token grant type right before constructing the GetFlowexecutionsQueryRequest. The Java client doesn’t auto-refresh unless you wire it up.
You’ll need to inject that token manually into the client instance.
PostOAuthApiTokenRequest refreshRequest = new PostOAuthApiTokenRequest();
refreshRequest.grant_type("refresh_token");
refreshRequest.refresh_token(currentRefreshToken);
ApiClient client = new ApiClient();
OAuthApi oauthApi = new OAuthApi(client);
PostOAuthApiTokenResponse response = oauthApi.postOAuthApiToken(refreshRequest);
client.setAccessToken(response.getAccess_token());
Make sure your app has analytics:read. The offset pagination fails if the token lacks permissions. I usually hit that wall with flow:read missing.
GetFlowexecutionsQueryRequest queryRequest = new GetFlowexecutionsQueryRequest();
queryRequest.setOffset(currentOffset);
queryRequest.setPageSize(25);
queryRequest.setSince(Instant.now().minusSeconds(86400));
queryRequest.setUntil(Instant.now());
FlowexecutionsQueryResponse response = platformClient.getFlowAnalyticsApi().getFlowexecutionsQuery(
queryRequest, null, null, null, null, null, null, "application/json"
);
currentOffset += response.getEntities().size();
Problem
The offset pagination fails because the request object isn't persisting the state. It happens when the query gets recreated inside the loop without carrying the previous count. The Java SDK requires `GetFlowexecutionsQueryRequest` to hold the cumulative offset. You'll also hit rate limits if the `pageSize` is too large, causing the API to drop the connection. The time range constraints need to be narrow, otherwise the query times out. This mirrors the chunking issue in the data export API where offsets drift during long runs. The `since` and `until` parameters must align with the flow execution retention policy. If you query too far back, the API rejects the request.
Error
A `400 Bad Request` appears when the offset is null or negative. You'll see `401 Unauthorized` if the token expires mid-stream. The error payload mentions "invalid pagination" if the offset drifts unexpectedly. Redshift COPY operations fail if the JSON lines are malformed, so validating the response entities before writing to S3 is crucial. You'll need to handle the token refresh logic separately. The offset resets if the connection drops, so caching the last successful offset in a local variable is mandatory. This prevents data loss during the ETL process.
Question
Verify the offset persistence logic manually.
Problem
Offset pagination gets rejected on this endpoint, so you’ll keep hitting 400s. The cursor approach actually works way better for high-volume pulls.
Code
queryRequest.setAfter(response.getAfterCursor());
FlowexecutionsQueryResponse nextBatch = platformClient.getFlowAnalyticsApi().getFlowexecutionsQuery(queryRequest, null, null, null, null, null, null, "application/json");
Error
400 Bad Request when offset exceeds the cursor threshold, kinda annoying when you’re batching requests.
Question
- Drop the offset parameter entirely since the flow analytics API enforces cursor-based pagination now
- Pass the
after_cursor field back to keep the stream alive without token expiry clashes
- Watch out for the 250 record page size limit, it’ll throttle your concurrent workers if you push it higher
PureCloudPlatformClientV2 actually handles the cursor fallback better than you’d expect, and it confirms the suggestion above works. You just have to explicitly drop the offset param or it throws that 400. The analytics endpoint doesn’t like offset and after in the same request body. You’ll get a validation error faster than you can blink. Here’s how the loop should look in the Java SDK:
GetFlowexecutionsQueryRequest queryRequest = new GetFlowexecutionsQueryRequest();
queryRequest.setSince(Instant.now().minus(Duration.ofHours(24)));
queryRequest.setUntil(Instant.now());
queryRequest.setPageSize(100);
while (true) {
FlowexecutionsQueryResponse response = platformClient.getFlowAnalyticsApi()
.getFlowexecutionsQuery(queryRequest, null, null, null, null, null, null, "application/json");
if (response == null || response.getEntities() == null || response.getEntities().isEmpty()) {
break;
}
// process response.getEntities()
String nextCursor = response.getAfterCursor();
if (nextCursor == null || nextCursor.isEmpty()) {
break;
}
queryRequest.setAfter(nextCursor);
}
Make sure you’re clearing offset before setting after. PureCloudPlatformClientV2 defaults the offset to zero if you don’t explicitly null it out, and the CXone backend treats that as a conflict. Just call queryRequest.setOffset(null) right before the loop starts. Also, watch your Authorization header rotation. That Bearer token expires in two hours, so wrap the whole thing in a token refresh check or you’ll hit a wall at 3 AM EST. The /api/v2/analytics/flowexecutions/query endpoint is strict about scope too. You need analytics:read on the client credentials, not just user:read.
Took me three days to figure out why the embeddable client app was silently dropping these calls until I swapped to cursor pagination. The Java SDK docs still show offset examples from 2021. Run that loop and the dataset pulls clean.