Beta
Retrying Safely
A request can time out, or the connection can drop, after the service has done the work but before you get the response. Retry it blindly and you can start a second session, stage the loan a second time, or start and pay for a second workflow.
An idempotency key makes the retry safe. You send a key of your choosing in the Idempotency-Key header. If you send the same request again with the same key, the service returns the response it gave the first time instead of doing the work again. That returned response is called a replay.
- Which calls accept a key
- Step 1: Make a key
- Step 2: Send it
- Step 3: Retry with the same key
- Step 4: Read the response
- Checking what happened
- When the 409 doesn’t go away
- When to make a new key
- Rules
Which calls accept a key
Every POST on the session and workflow APIs accepts an optional Idempotency-Key header. Without the header, each call behaves as its reference page describes.
| Call | Without a key, a retry | With a key, a retry |
|---|---|---|
| Start a Session | Starts a second session. | Returns the first session. |
| Stage a Loan | Stages the loan again. | Returns the first response and records nothing. |
| Start a Workflow in a Session | Starts and bills a second workflow. | Returns the first workflow. Nothing is billed twice. |
| Start a Workflow | Starts and bills a second workflow. | Returns the first workflow. Nothing is billed twice. |
| Reconfirm a Workflow | Starts and bills a second run. | Returns the first run. Nothing is billed twice. |
GET requests don’t need a key. Reading never changes anything, so a GET is always safe to retry.
Step 1: Make a key
Use a V4 UUID. A key can be 1 to 255 characters, not all spaces.
- Make a new key for every new request. Starting two different workflows needs two keys.
- Don’t put sensitive data in a key. Keys are stored as you send them, and unlike responses they are not encrypted. Never build a key from a borrower’s name, email address or loan number.
- Keys are matched ignoring case and surrounding spaces.
abcandABCare the same key. A UUID never runs into this.
Step 2: Send it
Add the key as a header. This starts a workflow with a key:
idempotency_key="$(uuidgen)"
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/sessions/0000000000013500001/workflows \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${idempotency_key:?set idempotency_key first}" \
-d '{
"product": { "service": "ADV", "model": "ADV-120" },
"correlationId": "my-workflow-0001"
}'
Step 3: Retry with the same key
If the request times out, or you get no response, send it again exactly as before: the same key, the same URL and the same body.
The service compares each retry with the first request sent under that key:
- The same request gets a replay. The order of fields inside a JSON object and whitespace don’t matter, and numbers compare by value, so
100and100.0are the same. The order of elements in an array, such asterms, does matter. - A different body under the same key is refused with
400 VALIDATION_ERRORonce the first request has finished. While the first is still running, it gets409 IDEMPOTENCY_IN_PROGRESSinstead. It never gets a replay. - The same key on a different call, or on a different session or workflow in the URL, is a separate request. The key doesn’t carry over. The URL is compared as sent, so a workflow ID with and without its
wfpop_prefix are different URLs.
Step 4: Read the response
The service stores the response it gives under a key and returns it to every retry: every 2xx, and a 5xx on the three workflow calls. A 4xx is never stored, and neither is a 5xx on Start a Session or Stage a Loan.
| You receive | It means | Do this |
|---|---|---|
No response, a timeout, or a 502, 503 or 504 | The request may or may not have reached the service. | Send the same request with the same key. This is the case keys exist for. |
A 2xx with the header X-Pps-Idempotent-Replay: true | A replay. An earlier request under this key did the work. | Use it as the result. |
| A 2xx without that header | The request ran now. | Use it as the result. |
409 with messages[0].code IDEMPOTENCY_IN_PROGRESS | An earlier request under this key is still running. | Wait the number of seconds in the Retry-After header, then send the same request with the same key. Stop after a few minutes and follow When the 409 doesn’t go away. |
Any other 409, such as SESSION_EMPTY or RECONFIRM_NOT_AVAILABLE | The request was refused for a reason a retry won’t fix. | Follow the call’s reference page. |
400 | The request was refused. The body is either VALIDATION_ERROR in messages or a problem document with no messages. | Depends on the cause; see When to make a new key. |
401 or 403 | Your credential didn’t work, or you aren’t allowed to make this call. A 403 with an HTML body came from the edge (the protection in front of the service) and never reached it. | Fix the credential or the request, then send it with the same key. |
404 | The session or workflow in the URL doesn’t exist. | Check the URL. |
429 | Too many requests. The edge refused the request, so it never reached the service. | Back off for a few seconds, then send the same request with the same key. |
500 on Start a Workflow, Start a Workflow in a Session or Reconfirm a Workflow | Unknown whether the work happened. The 500 is stored, so every retry under this key returns the same 500, marked X-Pps-Idempotent-Replay: true. | Check what happened (see Checking what happened). Send the request again under a new key only if it didn’t happen. |
500 on Start a Session or Stage a Loan | The failure is not stored. | Send the same request with the same key. It runs again. |
A replay is the original response, not the current state. A replayed Start a Session shows loanStaged as it was when the session started, and a replayed workflow start shows the workflow’s first status. Read the session or read the workflow for how things stand now.
A 409 looks like this:
{
"messages": [
{
"code": "IDEMPOTENCY_IN_PROGRESS",
"text": "A request with this Idempotency-Key is still running. Retry the same request with the same key after the Retry-After delay.",
"timestamp": "2026-09-25T03:51:05.896Z"
}
]
}
Retry-After is a whole number of seconds, such as 5. It is never a date.
Checking what happened
When you can’t tell from the response whether the work happened, check before you send the request under a new key.
Start a Workflow and Start a Workflow in a Session. Look for the workflow with Find Workflows, using the correlationId you sent:
curl "https://api.pointservices.com/riskinsight-services-ws/resources/v1/workflows?correlationId=my-workflow-0001" \
-H "Authorization: Bearer your_access_token_here" \
-H "Accept: application/json"
If a workflow comes back, use it. If none does, start it again under a new key. Send a distinct correlationId on every workflow start: it is how you find a workflow when you have no response.
Reconfirm a Workflow. The workflow always exists, so read it with Read a Workflow. If its runId differs from the one before you reconfirmed, or its status is processing, the reconfirm was accepted. If not, reconfirm again under a new key.
Start a Session and Stage a Loan. There is nothing to search for, and neither call is billed. If a 409 doesn’t go away, send the request under a new key: a new stage replaces the loan the session holds, and an unused session costs nothing.
When the 409 doesn’t go away
If IDEMPOTENCY_IN_PROGRESS keeps coming back for more than a few minutes, the first request may have finished without its response being stored. Stop retrying, check what happened, and continue under a new key only if the work didn’t happen.
When to make a new key
- Every new request. A key belongs to one request.
- After a stored 500, once you have checked that the work didn’t happen.
- After a 409 that doesn’t go away, once you have checked.
- After the key expires. A stored response is kept for 30 days by default, and your account may be configured differently. After that, the service treats a retry as a new request. For a workflow start, that means a second, billed workflow.
A 400 isn’t stored. What to send next depends on the cause:
- A problem in the body, such as a bad
termselement: fix it and send it with the same key. - A blank key, or one longer than 255 characters: send the request with a valid key.
- A key already used with a different body: the first request’s result is still stored under that key. Send the original body to get its replay, or check what happened before using a new key.
Rules
- Keys belong to you. A key is scoped to the user who sends it: another user sending the same value, even in your organization, makes a separate request, and nobody is ever sent another user’s response.
- Keys belong to one call. The same value on a different call, or on a different session or workflow in the URL, is a separate request.
- Keys pass through the edge. Like any header, a key can be refused by the edge if it looks like an attack, with a
403and an HTML body instead of JSON. - A replay has the same status, content type and body as the original.
X-Pps-Idempotent-Replay: truemarks it as a replay. Other headers, such as request IDs, are new on each response.