DEVELOPER GUIDE

View as Markdown

Beta

Getting Started

Authenticate, start a session, stage your data, start a workflow and read its result. Each step below is one call, and A complete run puts them together.

Collect what you need from PPS before you write any code; see What you need from PPS.

Register your webhook endpoint before you start any workflows. There is no back-fill. An endpoint registered later hears about future events only, and nothing replays what you missed.

Step 1: Authenticate

Every call carries a bearer token in the Authorization header.

curl -X POST https://api.pointservices.com/user-management-services-ws/oauth2/002/signInWithPassword \
  -H "Content-Type: application/json" \
  -d '{
    "username": "customerUsername",
    "password": "customerPassword"
  }'

Keep the access_token. Every call below sends it as Authorization: Bearer <token>. It expires after an hour, so build the refresh in now.

Refresh the token on a 401, and only on a 401: get a fresh token and retry once, and if that also fails, stop and surface the error rather than looping. An expiring token turns a 200 into a 401, never a 403. A 403 from the service is an authorization decision, and a new token won’t change it. A 403 with an HTML body comes from the edge instead, most often because a POST had no Content-Type.

Obtaining An Access Token covers refreshing and signing in again.

Step 2: Register your webhook endpoint

Do this first, before you start any workflows. The endpoint is how PPS tells you a result is ready.

curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints \
  -H "Authorization: Bearer your_access_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/pps/workflows",
    "organizationalUnitGuid": "OU41502",
    "displayName": "Workflow results",
    "acceptedEventTypes": [
      {
        "serviceName": "ADV",
        "modelName": "ADV-120",
        "eventTypes": ["workflow.updated", "workflow.failed"]
      }
    ],
    "contacts": ["integrations@example.com"],
    "signingSecret": "whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL"
  }'

Replace whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL with a secret you generate; PPS rejects the placeholder with a 400. Webhooks: Getting Started shows how.

A successful register returns 200, not 201.

{
  "id": 4711,
  "organizationalUnitGuid": "OU41502",
  "displayName": "Workflow results",
  "url": "https://hooks.example.com/pps/workflows",
  "enabled": true,
  "circuitState": "CLOSED",
  "receiving": true,
  "hasNoContactsRegistered": false,
  "signatureMode": "SYMMETRIC",
  "acceptedEventTypes": [
    {
      "serviceName": "ADV",
      "modelName": "ADV-120",
      "eventTypes": ["workflow.updated", "workflow.failed"]
    }
  ],
  "warnings": []
}

Check receiving, not enabled. A new endpoint is always enabled. receiving: true is what tells you an event fired right now would actually reach you.

Declare both topics. An endpoint that accepts workflow.updated without workflow.failed registers successfully and then never hears about a failed run.

Give PPS a contacts address. Repeated delivery failures disable an endpoint automatically, and with no contact registered nobody is told: deliveries stop and you find out when someone asks why results stopped arriving. The response reports this as hasNoContactsRegistered.

Getting Started with Webhooks covers choosing a signing secret and verifying what arrives.

Step 3: Choose a route

There are two ways to start a workflow and only one way to follow one.

  Direct Session
Call POST /v1/workflows with terms inline start a session, stage, then start a workflow
Calls to start one workflow one three
Starting two workflows against one set of data sends it twice sends it once
Building your data up over time afterwards, by staging into the session the start returns as workflow.sessionId re-stage as often as you need before you start

Use Start a Workflow for a single fire-and-forget workflow. Use a session when you will start more than one workflow against the same data, or when you assemble it across several steps. Both return the same workflow envelope, and everything after the start is identical. A direct start also creates a session: it stages your terms into it and returns its id as workflow.sessionId, so you can stage revised data into it later and start another workflow against it.

The rest of this page follows the session route.

Step 4: Start a session

curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/sessions \
  -H "Authorization: Bearer your_access_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "correlationId": "my-loan-0001"
  }'
{
  "session": {
    "sessionId": "0000000000013500001",
    "dataStaged": false,
    "correlationId": "my-loan-0001"
  },
  "messages": [
    {
      "code": "SESSION_STARTED",
      "text": "session started",
      "timestamp": "2026-08-07T15:01:42Z"
    }
  ]
}

Keep the sessionId. The next two calls take it. The session is empty and nothing is billed, so dataStaged is false until you stage.

Send Content-Type: application/json even though the body is optional. A POST that arrives without it is refused before it reaches the service, with a 403 nothing can explain to you.

A retried start without an Idempotency-Key starts a second session. Send a key and the retry returns the first session instead. See Retrying Safely.

Step 5: Stage the data

Staging records a snapshot of your data. Call it as often as you need — the newest snapshot is what a workflow will read. You stage data through the /sami path, using SAMI, PitchPoint’s own format for describing a loan.

curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/sessions/0000000000013500001/sami \
  -H "Authorization: Bearer your_access_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "terms": [
      {
        "person": {
          "firstName": "Jane",
          "lastName": "Sample",
          "ssn": "000-00-0000",
          "dob": "01/31/1980",
          "homePhone": "5555550100",
          "residences": [
            {
              "currentIndicator": true,
              "address": {
                "addressLine1": "12 Example Avenue",
                "addressLine2": "Apt 4B",
                "city": "Sampleton",
                "state": "NY",
                "postalCode": "00000"
              }
            }
          ]
        }
      }
    ]
  }'
{
  "session": {
    "sessionId": "0000000000013500001",
    "dataStaged": true,
    "correlationId": "my-loan-0001"
  },
  "messages": [
    {
      "code": "SESSION_UPDATED",
      "text": "loan snapshot recorded",
      "timestamp": "2026-08-07T15:01:42Z"
    }
  ]
}

dataStaged is now true. That is the thing to check here.

Each snapshot is complete, not a patch. Send your data as it now stands, in full. Anything you omit is absent from the new snapshot rather than inherited — which is how you remove a party. Keep your own copy of what you sent and build the next snapshot from that.

A 200 means your JSON was well formed. It does not mean your submission is complete, or that a product will accept it. Staging checks shape, not meaning: a field name this service does not recognize is ignored rather than refused.

Each product needs particular facts, and it checks for them when it runs. ADV needs at least one borrower with a residential address, a home phone and a date of birth — the example above has all three. For any other product, ask your PPS account manager what it requires; those requirements are not published, and staging will not tell you.

For the term format and worked examples, see SAMI v2 Terms; A large loan is a whole multi-party submission rather than the one-borrower example above. For the request and response contract, see Stage Data.

Step 6: Start a workflow

Starting a workflow names a product and nothing else. The data comes from the session.

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" \
  -d '{
    "product": {
      "service": "ADV",
      "model": "ADV-120"
    },
    "correlationId": "my-workflow-0001"
  }'

You get back a 202. Starting a workflow does not wait for the product to run: the call returns a handle within milliseconds, and you collect the result afterwards.

{
  "workflow": {
    "workflowId": "wfpop_0000000000002080001",
    "runId": "wfrunpop_0000000000002080001",
    "sessionId": "0000000000013500001",
    "correlationId": "my-workflow-0001",
    "product": {
      "service": "ADV",
      "model": "ADV-120"
    },
    "status": "processing",
    "reconfirmAvailable": false
  },
  "messages": [
    {
      "code": "ACCEPTED",
      "text": "workflow accepted for processing",
      "timestamp": "2026-08-07T15:01:42Z"
    }
  ]
}

Keep the workflowId — wfpop_0000000000002080001 here. Every other call takes it. It is stable for the life of the workflow, including across reconfirms.

This is the call that costs money. Stage once and start as many workflows as you need, but each workflow is independently billed, not a retry of the last one. A reconfirm is billed as its own workflow too. To retry a start that timed out without starting a second workflow, send an Idempotency-Key (Retrying Safely). If your product uses outcome-based billing instead, such as billing only for loans that fund, see Reporting Outcomes.

A 202 means the workflow was accepted for processing. Whether your submission satisfies the product is decided later, and one that does not fails the workflow rather than rejecting the request.

Start a Workflow in a Session has the full contract, what runId is for, and what a failed run records.

Step 7: Wait for the delivery

Nothing fired when you started the workflow. When the product finishes, PPS POSTs to the endpoint you registered in Step 2.

POST /pps/workflows HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350
webhook-timestamp: 1787927163
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=

{"type":"workflow.updated","eventId":"msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350","timestamp":"2026-08-07T15:03:11Z","data":{"workflowId":"wfpop_0000000000002080001","runId":"wfrunpop_0000000000002080001","correlationId":"my-workflow-0001","serviceName":"ADV","modelName":"ADV-120","status":"ready"}}

The same body, formatted:

{
  "type": "workflow.updated",
  "eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
  "timestamp": "2026-08-07T15:03:11Z",
  "data": {
    "workflowId": "wfpop_0000000000002080001",
    "runId": "wfrunpop_0000000000002080001",
    "correlationId": "my-workflow-0001",
    "serviceName": "ADV",
    "modelName": "ADV-120",
    "status": "ready"
  }
}

data.workflowId is the same wfpop_0000000000002080001 the 202 gave you in Step 6. That is how you match a delivery to the workflow that produced it. correlationId is the value you sent when you started it, so you can match on that instead if you prefer.

data.status is ready. A result exists and is worth fetching.

Verify the signature before you trust the body. An endpoint that acts on unverified requests accepts event data from anyone who finds its URL. See Verifying Signatures.

Receiving Deliveries covers what to answer, handling the same event twice, and what PPS does when your server is down.

Step 8: Read the result

Two reads are available, and they are siblings rather than a chain. Findings give you the result. The workflow gives you its state and, on a failure, the reason.

Both are callable at any time. The delivery does not unlock them — it tells you the call is now worth making.

flowchart TD
    A[Delivery arrives] --> B{Which topic?}
    B -->|workflow.updated| C[GET the findings]
    B -->|workflow.failed| D[GET the workflow]
    C --> E[There is data]
    D --> F[messages says why]

After a workflow.updated delivery, read the findings:

curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/findings/workflows/wfpop_0000000000002080001/findings" \
  -H "Authorization: Bearer your_access_token_here"
{
  "workflowId": "wfpop_0000000000002080001",
  "findings": [
    {
      "findingId": "fndg_0000000000006051384",
      "code": "HP.ST",
      "displayName": "High-priority stated income mismatch",
      "category": ["Income"],
      "references": [
        { "correlationId": "borrower-1", "type": "person" }
      ],
      "severity": "HIGH",
      "result": "Fail",
      "resultOrOverriddenResult": "Fail",
      "userMessage": ["Stated income does not match the verified source document."],
      "userSuggestion": ["Confirm the applicant's income against the attached document before proceeding."],
      "reviewed": false,
      "reviewRequired": true,
      "hasAttachments": true,
      "hasHistory": false
    }
  ]
}

This example leaves out each finding’s authorization; List a Workflow’s Findings shows the full shape.

Pass the workflowId exactly as you received it, wfpop_ prefix and all.

Calling findings early is fine. Before a result exists it returns nothing. That is the correct answer to a question asked early, not an error and not a reason to retry hard. Once a ready delivery has arrived, there will be data.

The workflow resource and a delivery use different vocabularies for the same moment: after a delivery carrying data.status: "ready", reading the workflow returns status: "complete". Both mean your results are ready. Prefer the delivery, which spares you polling.

Getting Started with Findings covers reading, filtering and reviewing findings.

A complete run

host="https://api.pointservices.com/riskinsight-services-ws/resources/v1"
tok="your_access_token_here"

# 1. register the webhook endpoint, once, before you start any workflows
curl -s -X POST "${host}/webhooks/endpoints" \
  -H "Authorization: Bearer ${tok}" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/pps/workflows","organizationalUnitGuid":"OU41502","acceptedEventTypes":[{"serviceName":"ADV","modelName":"ADV-120","eventTypes":["workflow.updated","workflow.failed"]}],"contacts":["integrations@example.com"],"signingSecret":"whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL"}'

# 2. start a session -> returns sessionId
curl -s -X POST "${host}/sessions" \
  -H "Authorization: Bearer ${tok}" \
  -H "Content-Type: application/json" \
  -d '{"correlationId":"my-loan-0001"}'

# 3. stage the data -> 200, dataStaged becomes true
curl -s -X POST "${host}/sessions/0000000000013500001/sami" \
  -H "Authorization: Bearer ${tok}" \
  -H "Content-Type: application/json" \
  -d @loan.json

# 4. start a workflow -> 202, returns the wfpop_ workflowId. THIS IS BILLED.
curl -s -X POST "${host}/sessions/0000000000013500001/workflows" \
  -H "Authorization: Bearer ${tok}" \
  -H "Content-Type: application/json" \
  -d '{"product":{"service":"ADV","model":"ADV-120"},"correlationId":"my-workflow-0001"}'

# 5. workflow.updated arrives at your endpoint, then read the findings
curl -s -X GET "${host}/findings/workflows/wfpop_0000000000002080001/findings" \
  -H "Authorization: Bearer ${tok}"

# 6. lost the handle? search the correlationId you sent when you started it
curl -s -X GET "${host}/workflows?correlationId=my-workflow-0001" \
  -H "Authorization: Bearer ${tok}"

Copyright © Pitchpoint Solutions. All rights reserved.