API REFERENCE
Beta
Reconfirm a Workflow
Runs a workflow again under its existing handle, either against the data it already has or against a revised set.
The workflowId does not change. A new runId is issued, and Read a Workflow afterwards serves the reconfirm’s progress and then its results. From the outside you are reconfirming the same workflow, at the same URL, with the same handle.
Get the URL to submit against from your Pitchpoint Account Representative.
A reconfirm is billed as its own workflow, not a free retry. It is not a way to re-fetch results you already have — for that, read the workflow, which costs nothing.
Sample
url="https://api.pointservices.com/riskinsight-services-ws/resources/v1/workflows/wfpop_0000000000002080001"
curl -X POST "${url}" \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"correlationId": "alpha_numeric_string_to_help_identify_this_reconfirm"
}'
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your token | true |
| Content-Type | application/json | true |
| Accept | application/json | false |
| X-PPS-UserAlias | your value | false |
| X-PPS-UserAgent | your value | false |
| X-PPS-CorrelationID | your value | false |
Send Content-Type: application/json on every POST, including one with no body at all. In deployed environments a request that arrives without it is refused by edge protection in front of the application, with a 403 carrying an HTML body rather than any response documented here — and because the service never sees the request, nothing can explain it to you. A local or unprotected deployment may accept the same request, so do not take a working call there as evidence the header is optional.
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.
Path Parameters
| Property | Description | Type |
|---|---|---|
| workflowId | (mandatory) The wfpop_ handle of the workflow to reconfirm. Accepted with or without the prefix. A wfrunpop_ runId addresses nothing and will not work here. | string |
Check reconfirmAvailable first
Read the workflow and check workflow.reconfirmAvailable before posting here.
The gate is the reconfirmAvailable flag, never the presence of links.reconfirm — that link is published on every workflow envelope whether or not a reconfirm is actually available, so its presence tells you nothing. Compare the flag against true explicitly: it is optional on the wire, and an absent field means “not available”, not an error.
reconfirmAvailable: false does not necessarily mean the product can never be reconfirmed. A product offering several follow-ups has no single default, so the operation declines to choose rather than guessing. Read the flag as “a reconfirm is available here with no further input from you.”
Request Data Properties
The body is optional, and which one you send decides what the workflow runs against.
| You send | The workflow runs against |
|---|---|
no body, an empty body, whitespace only, or {} | the data it already has |
{"correlationId": "..."} | the data it already has, with this run tagged by your value |
{"terms": [ ... ]} | revised data — a complete restatement, not a patch |
{"product": { ... }} | nothing. Rejected with 400. |
null, a JSON array, a bare string, or malformed JSON | nothing. Rejected with 400. |
| Property | Description | Type |
|---|---|---|
| terms | (optional) A complete restatement of the loan. Anything you leave out is absent from the new snapshot, not inherited. Omit the key entirely to leave the loan unchanged — do not send [], which is rejected. | array |
| correlationId | (optional) Your own reference for this run, at most 255 characters. Never inherited from the original run. | string |
Do not name a product. It is inherited from the workflow you are reconfirming, and a body carrying product is rejected. The service runs that product’s one default follow-up — for ADV, a RefreshOrder, even when the original order was a FirstOrder. Expect productName to change on the next read; that is the follow-up, not a mistake.
Send Content-Type: application/json even when you send no body. In deployed environments a request that omits it is refused by edge protection with a 403 carrying an HTML body, before this service sees it.
Webhook deliveries from a reconfirm
A reconfirm fires its own events, exactly as the first run did. The delivery carries the same workflowId and the new runId, so a handler keyed on workflowId needs no special case for reconfirms.
correlationId belongs to the run, not the workflow, and is not inherited. Send a new one on the reconfirm body if you match deliveries on it; otherwise the delivery carries no correlationId at all.
Receiving Deliveries covers the envelope and the two ids it carries.
Responses
Not every response carries the same body, so branch on the status code first, and then on what the body actually contains. Some responses carry the messages envelope described on this page; others carry a JSON problem document with an entirely different set of fields; others 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.
202
Accepted for processing. The same workflowId, a new runId, and status back to processing.
| Property | Description | Type |
|---|---|---|
| workflow | (present) The workflow, now running its reconfirm. Same shape as a start acknowledgment. | object |
| messages | (present) Always an array, never null. Carries one message whose code is ACCEPTED. | array |
| links | (present) self and reconfirm, unchanged — identifiers and URLs stay stable across reconfirms. | object |
As with any acknowledgment, no timestamps are set and reconfirmAvailable is false. Whether it returns to true after this run depends on the product, as described above.
Example:
{
"workflow": {
"workflowId": "wfpop_0000000000002080001",
"runId": "wfrunpop_0000000000002080002",
"sessionId": "0000000000013500001",
"correlationId": "alpha_numeric_string_to_help_identify_this_reconfirm",
"product": {
"service": "ADV",
"model": "RefreshOrder"
},
"productType": "ADV",
"productName": "RefreshOrder",
"status": "processing",
"reconfirmAvailable": false
},
"messages": [
{
"code": "ACCEPTED",
"text": "workflow accepted for reconfirmation",
"timestamp": "2026-08-07T16:20:04Z"
}
],
"links": {
"self": {
"href": "https://api.pointservices.com/riskinsight-services-ws/resources/v1/workflows/wfpop_0000000000002080001"
},
"reconfirm": {
"href": "https://api.pointservices.com/riskinsight-services-ws/resources/v1/workflows/wfpop_0000000000002080001",
"method": "POST"
}
}
}
Note the runId differs from the original run’s while the workflowId is unchanged. That is the whole lineage model: one handle, many runs.
400
The reconfirm was refused and nothing was billed. Causes: a body carrying product; "terms": []; a body that is null, an array, a bare string, or malformed JSON; a correlationId longer than 255 characters. Also a workflow whose product no longer resolves to a reconfirmable product — a 400 you can meet without sending anything wrong.
A 400 arrives in two different body shapes, decided by how far the request got before it was refused. Check for a messages array first, and fall back to the problem document when it is absent.
A value this service evaluated and rejected carries the messages envelope with 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 reconfirm request must not name a product",
"timestamp": "2026-08-07T15:01:42Z"
}
]
}
A body that could not be read as this request at all — a JSON array where an object was expected, a field of the wrong type, or malformed JSON — is refused before the service evaluates any value, so there is nothing to put in a messages array. It carries a JSON problem document instead, with Content-Type: application/problem+json and no messages key and no VALIDATION_ERROR code.
Example:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Request body has a field that must be a value of type SessionStartRequest",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
A client that reads messages[0].code on every 400 without checking that messages exists will throw on this second shape. Branch on the presence of messages, not on the status.
No run is started and the workflow is left exactly as it was.
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.
Treat a 401 and a 403 alike: both mean your credential did not work, and neither distinguishes reliably enough to build separate recovery paths on. 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 reconfirm this workflow. No run is started and nothing is billed.
The body does not carry the messages envelope, so there is no code to branch on. Branch on the status alone, and treat the body as opaque: do not assume it is empty, and do not assume it is JSON.
A 403 is not always a permissions problem — try a fresh token first. A credential the service will not honour can be refused with either status, so a sudden wall of 403s on calls that worked a moment ago is more often an expired token than a change to your entitlements. Obtain a new access token and retry once. If it fails again, then treat it as entitlement and talk to your Account Representative.
A 403 is also produced at the edge, before this service is reached, when a request omits Content-Type or carries a value resembling SQL injection or cross-site scripting. Those refusals carry an HTML body and this service never sees them. A value that legitimately contains such text should be encoded — Base64, for instance — rather than sent raw.
404
No workflow exists with this identifier, or the path you called is not served. Check you are using the wfpop_ workflowId and not a wfrunpop_ runId.
A 404 arrives in two different body shapes, decided by whether the path is served at all.
A path this service serves, naming something that does not exist carries a JSON problem document with Content-Type: application/problem+json. Read the machine-readable part from type.
| Property | Description | Type |
|---|---|---|
| type | (present) An absolute URI identifying the problem, ending /problems/not-found here. | string |
| title | (present) Short human-readable summary. | string |
| status | (present) Repeats the HTTP status code. | number |
| detail | (present) Human-readable explanation naming what was not found. Never machine-parsed. | string |
| instance | (present) An identifier for this one request. Quote it when contacting support. | string |
Example:
{
"type": "https://pointservices.com/problems/not-found",
"title": "No such resource",
"status": 404,
"detail": "Workflow[wfpop_0000000000002080001] not found",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
An identifier this API has already issued to you is readable the moment you receive it, so a 404 on one is final rather than a race. Retrying will not make it appear.
A path that is not served at all — most often a URL missing the riskinsight-services-ws context root — is refused by infrastructure in front of the application, which never sees the request. That response carries no body this API defines, so branch on the status alone and do not attempt to parse what comes back. Its exact shape depends on the environment you are calling.
If a 404 has no body, check the context root before anything else.
409
RECONFIRM_NOT_AVAILABLE — workflow.reconfirmAvailable is false. This is the one error response on this API that carries the full envelope, including the workflow block and links, so you can read the current state from the refusal itself. Do not retry on a timer: wait until a read shows reconfirmAvailable is true, then post once.
WORKFLOW_WRITE_CONFLICT — a concurrent change was detected and this attempt lost the race. Final for that attempt: the service does not retry it, so you decide whether to resubmit. No code path emits this today — concurrent changes are serialized server side, and what is not yet built is the translation of a lost race into this response rather than a generic failure. Handle it anyway: the code is part of the permanent vocabulary and may begin appearing without notice.
Branch on messages[].code, never on the status alone. A 409 on this surface means more than one thing, and the corrective action differs by cause.
| Property | Description | Type |
|---|---|---|
| messages | (present) Always an array, never null. | array |
| messages[].code | (present) One of the codes described above for this status. | string |
| messages[].text | (present) Human copy. Never machine-parsed. | string |
| messages[].timestamp | (present) RFC 3339 UTC with a trailing Z, fractional seconds only when they are not zero. | string |
A conflict can also arrive without a messages array, as a JSON problem document carrying type, title, status, detail and instance. When messages is absent, read the machine-readable part from the last path segment of type. Treat the conflict as final for that attempt in either shape, and check that messages is present before reading messages[0].code.
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.
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 does not carry the messages envelope, so branch on the status alone.
A 500 means the outcome is unknown, not that nothing happened. A failure raised after a write has already committed leaves that write in place. A reconfirm may have been started and billed. Do not simply retry. Read the workflow: if runId has changed or status is processing, the reconfirm was accepted. Retrying blind is how one order becomes two, and nothing on this surface removes a duplicate for you.