Beta
Find Workflows
Finds the workflows a given correlationId was supplied to. This is how you recover a workflow handle you did not keep — after a timeout, a crash, or a 500 whose outcome you could not determine.
It is a search, not a lookup. An empty result with a 200 is the ordinary “nothing matched” answer; a 404 is never used for it.
Get the URL to submit against from your Pitchpoint Account Representative.
Sample
url="https://api.pointservices.com/riskinsight-services-ws/resources/v1/workflows"
curl -X GET "${url}?correlationId=alpha_numeric_string_to_help_identify_this_workflow" \
-H "Authorization: Bearer your_access_token_here" \
-H "Accept: application/json"
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your token | true |
| Content-Type | application/json | false, but recommended |
| Accept | application/json | false |
| X-PPS-UserAlias | your value | false |
| X-PPS-UserAgent | your value | false |
| X-PPS-CorrelationID | your value | false |
This call sends no body, so Content-Type is not required. Sending it anyway costs nothing and is the safer habit: on the POST calls edge protection in deployed environments refuses a request that omits it, with a 403 carrying an HTML body rather than any response documented here.
Accept is not enforced — a request without it is answered normally with JSON — but send it anyway, so that a future change to content negotiation cannot alter what you receive.
X-PPS-CorrelationID is a support-tracing header and is not the same thing as the correlationId field in a request body. Only the body field is recorded against an order and searched by Find Workflows; setting the header globally in your HTTP client does not make your orders findable.
See the Common Headers guide for details on the optional X-PPS-* headers above.
Query Parameters
| Property | Description | Type |
|---|---|---|
| correlationId | (mandatory) The value to search for. Absent or blank is rejected. A value longer than 255 characters is not rejected, but no stored correlationId is that long, so it matches nothing. URL-encode it. A value that resembles SQL or markup is refused at the edge with no JSON body, so if you encoded such a value when you sent it (Base64, for instance), search for the encoded form. | string |
How matching works
- The whole value only. Never a prefix, never a substring. A
correlationIdthat shares a prefix with yours does not match, so an empty result usually means the value differs somewhere rather than that the run is gone. - Case sensitivity follows the data store’s collation, so do not rely on case alone to tell two ids apart.
- Results are newest first, and capped at 100. There is no paging.
The X-PPS-CorrelationID header is not searched here either. Only the correlationId field in a request body is recorded against a workflow. Setting that header globally in your HTTP client is a common and invisible mistake: your calls succeed, and your workflows are simply never found.
A session’s own correlationId is never searched here. Only values supplied when starting a workflow are — on Start a Workflow in a Session, Start a Workflow, or a reconfirm. Searching for the id you gave a session returns an empty list, correctly.
Two kinds of absence look identical to a bug. A workflow you are not entitled to read is silently absent rather than a 403, and does not count against the 100. Separately, the search reads at most the newest 1,000 matching runs across all accounts, so a value that other accounts reuse heavily can hide your older workflows behind theirs. In both cases the remedy is the same: make your correlation ids distinctive, so the search never has to sift a crowded value. There is no paging to fall back on.
Responses
Not every response carries the same body. Branch on the Content-Type first, and then on what the body contains. application/json is the messages envelope described on this page: read messages[].code. application/problem+json is a problem document, with type, title, status, detail and instance and no messages key: read type, and compare it as the whole URI (for example https://pointservices.com/problems/concurrent-modification). Some rejections carry no JSON at all. A client that calls its JSON parser on every non-2xx body, or that reads messages[0].code without first checking that messages is present, will fail on the responses it is most likely to meet. See Error handling for the whole rule.
200
| Property | Description | Type |
|---|---|---|
| workflows | (present) Always an array, never null. Empty when nothing matched — that is a success, not an error. | array |
| messages | (present) Always an array, never null. Empty on a successful search. | array |
Each element of workflows is a full workflow envelope, the same shape Read a Workflow returns — so the fields live one level down, under .workflow, and each entry carries the terms its run was started against.
Each entry describes the workflow’s current state, which is not necessarily the run your correlationId was supplied to. If you tagged an original run and the workflow has since been reconfirmed, the entry you get back describes the reconfirm.
Example, one match:
{
"workflows": [
{
"workflow": {
"workflowId": "wfpop_0000000000002080001",
"runId": "wfrunpop_0000000000002080001",
"sessionId": "0000000000013500001",
"correlationId": "alpha_numeric_string_to_help_identify_this_workflow",
"product": {
"service": "ADV",
"model": "FirstOrder"
},
"productType": "ADV",
"productName": "FirstOrder",
"status": "complete",
"reconfirmAvailable": true,
"serviceable": true,
"ordered": "2026-08-07T15:01:42Z",
"fulfilled": "2026-08-07T15:03:11Z",
"completed": "2026-08-07T15:03:11Z"
},
"terms": [
{
"person": {
"firstName": "Jane",
"lastName": "Sample"
}
}
],
"messages": [
{
"code": "SUCCESS",
"text": "Success",
"timestamp": "2026-08-07T15:03:11Z"
}
]
}
],
"messages": []
}
Example, nothing matched — a normal 200:
{
"workflows": [],
"messages": []
}
400
correlationId was absent or blank.
A 400 arrives in two different body shapes, decided by how far the request got before it was refused. Branch on the Content-Type: application/json is the messages envelope, and application/problem+json is a problem document.
A value this service evaluated and rejected carries the messages envelope, with Content-Type: application/json and the code VALIDATION_ERROR.
| Property | Description | Type |
|---|---|---|
| messages | (present) Always an array, never null. Carries one message whose code is VALIDATION_ERROR. | array |
| messages[].code | (present) Always VALIDATION_ERROR on this shape. | string |
| messages[].text | (present) Human-readable description of what was wrong. Never machine-parsed — the wording is not contractual. | string |
| messages[].timestamp | (present) RFC 3339 UTC with a trailing Z. Fractional seconds appear only when they are not zero, so parse this value rather than slicing it. | string |
Example:
{
"messages": [
{
"code": "VALIDATION_ERROR",
"text": "A workflow search must carry a non-blank correlationId query parameter",
"timestamp": "2026-08-07T15:01:42Z"
}
]
}
A request refused before it was evaluated carries a problem document instead, with Content-Type: application/problem+json and no messages key and no VALIDATION_ERROR code. This call takes no request body, so the only such case is a Content-Type header naming a character set this service cannot use, which carries the type https://pointservices.com/problems/invalid-request.
A client that reads messages[0].code on every 400 without checking that messages exists will throw on the problem-document shape. Branch on the Content-Type, or on the presence of messages, not on the status.
No search is performed.
401
No credential the platform could accept reached the service.
The body is a JSON problem document, not the messages envelope, and the response carries a WWW-Authenticate: Bearer realm="Helix" challenge. Read the machine-readable part from type; title and detail are human copy and are not contractual.
| Property | Description | Type |
|---|---|---|
| type | (present) An absolute URI identifying the problem. about:blank when no credential was sent at all. | string |
| title | (present) Short human-readable summary. | string |
| status | (present) Repeats the HTTP status code. | number |
| detail | (present) Human-readable explanation. Never machine-parsed. | string |
| instance | (present) An identifier for this one request. Quote it when contacting support. | string |
Examples:
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"detail": "Authentication is required. Present a bearer access token in the Authorization header.",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
{
"type": "https://pointservices.com/problems/invalid-token",
"title": "The access token was not accepted",
"status": 401,
"detail": "The access token was rejected.",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
The second form carries WWW-Authenticate: Bearer realm="Helix", error="invalid_token", and is what a token the service will not accept returns.
The invalid-token type deliberately does not say why the token was refused: the platform cannot tell an expired token from a revoked, badly signed or malformed one. Treat every invalid-token the same way.
Refresh your token on a 401, and only on a 401. An expiring token moves a call from 200 to 401, never to 403. Obtain a fresh access token and retry the request once. If it fails again, stop and surface the error rather than looping — repeated attempts with a credential the service has already refused will meet the rate throttle described below.
403
Your credential was understood but you are not permitted to search on this account. Note that a workflow you cannot read is omitted from a successful result rather than causing this status.
A 403 is an authorization decision: your identity was established and the answer is still no. It is never a statement about your token, so refreshing the token will not turn it into a 200. A refused request leaves nothing behind, so there is nothing to clean up.
The body is a problem document (application/problem+json), not the messages envelope, so there is no code to branch on. Its type is one of two values:
type | Meaning |
|---|---|
about:blank | The request was denied before the operation itself ran. |
https://pointservices.com/problems/not-authorized | A policy decision about the specific resource or product you named. |
Example:
{
"type": "https://pointservices.com/problems/not-authorized",
"title": "Not authorized",
"status": 403,
"detail": "The authenticated identity is not permitted to perform this action.",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
A 403 can also come from the edge, before this service is reached, when a request omits Content-Type or carries a value resembling SQL injection or cross-site scripting. That refusal carries an HTML body, not a problem document, and this service never sees it. Check the Content-Type of the response before you parse it. A value that legitimately contains such text should be encoded — Base64, for instance — rather than sent raw.
404
The path you called is not served. Check that the URL starts with the /riskinsight-services-ws/resources/v1 path prefix. A search that matches nothing is a 200 with an empty array, never a 404.
This path takes no identifier, so a 404 here means the path itself is not served: a wrong host, or an environment where this API is not deployed. The body is a problem document (application/problem+json) with type about:blank.
Example:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
A URL missing the /riskinsight-services-ws/resources/v1 path prefix does not reach this API at all. The 404 then comes from infrastructure in front of it, and carries no body this API defines, so don’t try to parse it. If a 404 has no problem document, check the path prefix before anything else.
429
Refused by a rate throttle at the edge, before this service is reached. The throttle counts requests per source address across every endpoint on both the session and workflow surfaces. Because the edge sends this response before the request reaches the service, it is not described in the OpenAPI specifications or the API Explorer.
The refusal carries no body, no messages array and no Retry-After header, and this service never sees it, so it will not appear in any support trace.
Back off and resume. When following a workflow to completion, an interval that stays clear of the throttle is two seconds for the first two checks, then five, then ten seconds thereafter, with an overall deadline after which you stop and investigate.
500
An unhandled failure inside the service. The body is a problem document (application/problem+json) with type about:blank, not the messages envelope, so branch on the status alone. When you report the error to PPS, quote the document’s instance: it carries the same id as the x-pps-request-id response header, and it is what lets PPS find this exact request.
Retrying this call is safe. This call changes nothing, so it is always safe to search again.