Beta
Start a Session
Starts an empty session and returns its identifier. A session is a workspace that holds one set of data: you stage that data into it, then start one or more workflows against it.
A sessionId is only ever issued by this service: here, or as workflow.sessionId when you start a workflow without a session. It is never derived, guessed or constructed by a client.
Get the URL to submit against from your Pitchpoint Account Representative, along with the products your account is entitled to use.
Sample
url="https://api.pointservices.com/riskinsight-services-ws/resources/v1/sessions"
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_session"
}'
Send Content-Type: application/json even when you send no body. The request body here is optional, and a client built to send nothing at all will often drop the header along with it. In deployed environments edge protection refuses a request that omits the header, answering 403 with an HTML body — not any response documented on this page, and not something this service can explain, because it never sees the request. A local or unprotected deployment may accept the same call, so test against a deployed environment before assuming the header is optional.
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 |
| Idempotency-Key | a V4 UUID | 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.
Idempotency-Key makes a retry of this call safe: send the same request again with the same key and you get the first response back, marked X-Pps-Idempotent-Replay: true, instead of the work being done twice. Use a new V4 UUID for each request. A key can be 1 to 255 characters; never put sensitive data in it. See Retrying Safely.
See the Common Headers guide for details on the optional X-PPS-* headers above.
Request Data Properties
The body is optional. An absent body, an empty body, a body of only whitespace and {} all mean the same thing, and all start a session with a correlation id this service generates for you. Fields this call doesn’t define are ignored.
| Property | Description | Type |
|---|---|---|
| correlationId | (optional) Your own reference for this session, at most 255 characters. It is not required to be unique. Leading and trailing whitespace is trimmed and a blank value is treated as absent. Longer than 255 characters is rejected. | string |
This session’s correlationId is not inherited by the workflows you later start against it, and it is not searchable. It describes the session only. Find Workflows searches the correlation ids supplied when starting a workflow, never this one. To find a workflow later, supply a correlationId when you start it itself.
A value that resembles SQL or HTML is refused at the edge with no explanation, so encode such values — Base64, for instance — rather than sending them raw.
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.
201
The session exists and is ready to stage data into. dataStaged is always false here.
A retry under the same Idempotency-Key returns this same 201, byte for byte, with the header X-Pps-Idempotent-Replay: true, naming the same sessionId. A replay’s dataStaged is still false, even if you have staged since. See Retrying Safely.
| Property | Description | Type |
|---|---|---|
| session | (present) The session that was started. See section below. | object |
| messages | (present) Always an array, never null. Carries one message whose code is SESSION_STARTED. | array |
session
| Property | Description | Type |
|---|---|---|
| sessionId | (present) The identifier for this session, used in the path of every later call against it. Treat it as opaque: store and echo it, never parse it. | string |
| dataStaged | (present) Whether anything has been staged. Always false on a newly started session. Once it becomes true it never returns to false. | boolean |
| correlationId | (present) Your value when you supplied one, otherwise a value this service generated for the session. | string |
Example:
{
"session": {
"sessionId": "0000000000013500001",
"dataStaged": false,
"correlationId": "alpha_numeric_string_to_help_identify_this_session"
},
"messages": [
{
"code": "SESSION_STARTED",
"text": "session started",
"timestamp": "2026-08-07T15:01:42Z"
}
]
}
A retried start without an Idempotency-Key starts a second session. Send a key and the retry returns the first session instead. A session that is never staged into is inert and costs nothing, but a client should not start more sessions than it stages data into. Nothing is billed until you start a workflow.
400
The request was refused. The causes return different body shapes, and on this endpoint the problem-document shape is the one you will meet most often:
| Cause | Body |
|---|---|
correlationId longer than 255 characters | the messages envelope, VALIDATION_ERROR |
| A body that is not a JSON object — an array, a bare value, or malformed JSON | a JSON problem document, about:blank, no messages key |
A literal null body. It is not treated as an absent body. | a JSON problem document, about:blank, no messages key |
A correlationId that can’t be read as a string, such as an object or an array | a JSON problem document, about:blank, no messages key |
A Content-Type naming a character set this service cannot use | a JSON problem document, https://pointservices.com/problems/invalid-request, no messages key |
An Idempotency-Key that is blank, longer than 255 characters, or already used with a different body | the messages envelope, VALIDATION_ERROR |
Only the correlationId length and the Idempotency-Key are values this endpoint evaluates. Everything else fails before evaluation, so a client that reads messages[0].code on every 400 breaks on the majority of them here.
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 session start request's correlationId must be at most 255 characters",
"timestamp": "2026-08-07T15:01:42Z"
}
]
}
A request refused before any value was evaluated carries a problem document instead, with Content-Type: application/problem+json and no messages key and no VALIDATION_ERROR code. Its type tells you which case it is:
| Cause | type |
|---|---|
A body that could not be read as a session start request at all: malformed JSON, a literal null body, a JSON array or bare value where an object was expected, or a field that can’t be read as its type | about:blank |
A Content-Type header naming a character set this service cannot use. It is refused before the body is read, so it says nothing about what you put in it. | https://pointservices.com/problems/invalid-request |
Example:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Malformed JSON",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
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 session is started, and no session object accompanies either shape.
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 start a session on this account. No session is started.
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, and that the host is right.
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.
409
IDEMPOTENCY_IN_PROGRESS — an earlier request under the same Idempotency-Key is still running. This is the one 409 to retry: wait the number of seconds in the Retry-After header, then send the same request with the same key. If it persists for more than a few minutes, stop and follow When the 409 doesn’t go away.
A lost optimistic lock can also answer 409, as a problem document whose type ends /problems/concurrent-modification, with no messages and no Retry-After. It is final for that attempt: start a session again.
Branch on the Content-Type first, and only then on messages[].code. A 409 here arrives in two body shapes, and the corrective action differs by cause. Never branch on the status alone, and never read messages[0].code without checking that messages is present.
application/json carries the messages envelope:
| 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 |
application/problem+json carries a problem document with type https://pointservices.com/problems/concurrent-modification and no messages key. Another request changed the same resource while yours was in flight, and nothing from yours was applied. It carries no Retry-After, deliberately: read the resource again, decide whether your change still applies to what you now see, and resubmit only if it does. Don’t resend the same request on a timer.
Example:
{
"type": "https://pointservices.com/problems/concurrent-modification",
"title": "Concurrent modification",
"status": 409,
"detail": "The resource was modified by another request while this one was being processed. This request was not applied; re-read the resource before resubmitting.",
"instance": "urn:pps:request:0000000000000000000000000000000000000000000000000000"
}
In either shape the conflict is final for that attempt: the service does not retry it for you. The one exception is IDEMPOTENCY_IN_PROGRESS, which always arrives in the messages envelope and which you retry after the Retry-After delay.
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. Starting a session is never billed. Send the same request again with the same Idempotency-Key: a 500 on this call is not stored, so if the failed attempt’s session was kept you get its original response, and if not the start runs again. Either way you end up with one session. Without a key, a retry can start a second session.