1. List User Teams
The first API call to make is List User Teams. This endpoint returns the teams associated with your user account, including theteam_uuid, division_uuid, and available workflow statuses for each team.
Example code:
clear_and_triage.py
Copy
Ask AI
import os
import time
from datetime import datetime, timezone
from pathlib import Path
import requests
from pingintel_api.pingvision import types as t
POLL_INTERVAL = 15 # seconds
def color_text(text: str, color: str) -> str:
"""Wrap text in ANSI color codes for terminal output."""
colors = {"red": "\033[31m", "yellow": "\033[33m", "green": "\033[32m"}
if color not in colors:
return text
reset = "\033[0m"
return f"{colors[color]}{text}{reset}"
# Configuration
API_KEY = os.environ.get("PINGVISION_AUTH_TOKEN")
BASE_URL = "https://vision.pingintel.com/api/v1"
headers = {"Authorization": f"Token {API_KEY}"}
# File to submit
file_path = Path("demo_email.eml")
files = [("files", (file_path.name, open(file_path, "rb")))]
## ...
# 1. List User Teams
# https://docs.pingintel.com/ping-vision/user-memberships/list-user-teams
list_teams_url = f"{BASE_URL}/user/teams/"
list_teams_response = requests.get(list_teams_url, headers=headers)
if list_teams_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to list teams: {list_teams_response.status_code}")
teams = list_teams_response.json()
team = teams[0] # Select team by index, or filter by team_name/company_name
team_uuid = team["team_uuid"]
division_uuid = team["division_uuid"]
status_uuids = {s["name"]: s["uuid"] for s in team.get("statuses", [])}
## ...
JSON output
Copy
Ask AI
[
{
"company_name": "Ping Intel",
"company_short_name": "PING",
"company_uuid": "12345678-abc1-cde2-efg3-123456789abc",
"division_name": "Ping Intel",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"id": 1,
"membership_type": "admin",
"statuses": [
{
"category": "Received",
"color": "#06A77C",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1001,
"is_valid_for_user_transition": false,
"name": "Received",
"uuid": "019c6d26-4468-7b89-8328-d840dc9b5e3b"
},
{
"category": "Initializing",
"color": "#8239FC",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1002,
"is_valid_for_user_transition": false,
"name": "Initializing",
"uuid": "019c6d26-4471-7e1f-a9b3-12acce3b0ba5"
},
{
"category": "Ready",
"color": "#F16704",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1003,
"is_valid_for_user_transition": true,
"name": "Waiting for Scrubbing",
"uuid": "019c6d26-4473-71f0-b372-b2a9349128f4"
},
{
"category": "In progress",
"color": "#0E85E0",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1004,
"is_valid_for_user_transition": true,
"name": "Waiting for Peer Review",
"uuid": "019c6d26-4476-75c3-8a87-ed72a31a36b3"
},
{
"category": "Closed",
"color": "#FFE523",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1005,
"is_valid_for_user_transition": true,
"name": "Reject",
"uuid": "019c6d26-4478-769b-9e91-fd7c2b961959"
},
{
"category": "Completed",
"color": "#0E85E0",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"iconName": "PlaceholderPing",
"id": 1006,
"is_valid_for_user_transition": true,
"name": "Completed",
"uuid": "019c6d26-447a-7dc5-ac39-ea2251c2f5d5"
}
],
"team_name": "Ping Intel",
"team_uuid": "9876543-abc1-cde2-efg3-123456789abcd"
},
"..."
]
2. Create Submission
The second API call to make is Initiate New Submission. This endpoint allows the user to upload files (such as.eml or .msg email files, SOVs, or other documents) to create a new submission for processing.
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 2. Create Submission
# https://docs.pingintel.com/ping-vision/create-submission/initiate-new-submission
payload = {
"client_ref": "my_salesforce_id", # Optional: external reference ID
"insured_name": "Acme Corp", # Optional: insured name
"team_uuid": team_uuid, # Required: team ID
}
create_submission_url = f"{BASE_URL}/submission"
create_submission_response = requests.post(create_submission_url, data=payload, files=files, headers=headers)
if create_submission_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to create submission: {create_submission_response.status_code}")
submission_id = create_submission_response.json()["id"]
print(f"Created submission: {color_text(submission_id, 'green')}")
## ...
JSON output
Copy
Ask AI
{
"id": "p-mk-ourke-esw5ab",
"message": "OK",
"url": "http://vision.pingintel.com/submission/i/p-mk-ourke-esw5ab"
}
3. Poll for Status Changes via List Submission Events
The third API call to make is List Submission Events. This endpoint allows the user to monitor status changes on the submission by polling for events. Filter bypingid to track a specific submission.
To query on page size, team, and division, include those parameters as well.
This URL will poll for events related to the provided submission, team, and division starting from February 20, 2026 (the date of this example submission):
https://vision.pingintel.com/api/v1/submission-events?pingid={id}&start=20260220000000&page_size=50&team={team_uuid}&division={division_uuid}
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 3. Poll for Status Changes via List Submission Events
# https://docs.pingintel.com/ping-vision/get-submission-data/list-submission-events
earliest_allowed_time = datetime.now(timezone.utc)
last_cursor_id = None
current_status = None
submission_events_url = f"{BASE_URL}/submission-events"
submission_activity_url = f"{BASE_URL}/submission"
print(f"Polling for status changes on submission {color_text(submission_id, 'yellow')}...")
while True:
events_params = {
"pingid": submission_id,
"start": earliest_allowed_time.strftime("%Y%m%d%H%M%S"),
"page_size": 50,
"team": team_uuid,
"division": division_uuid,
}
if last_cursor_id:
events_params["cursor_id"] = last_cursor_id
events_response = requests.get(submission_events_url, params=events_params, headers=headers)
if events_response.status_code not in (200, 201):
print(color_text(f"Error polling events: {events_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
events_json = events_response.json()
last_cursor_id = events_json.get("cursor_id")
# Process status change events
for event in events_json.get("results", []):
if event.get("event_type") == t.SUBMISSION_EVENT_LOG_TYPE.SUBMISSION_STATUS_CHANGE:
old_status = event.get("old_value", "")
new_status = event.get("new_value", "")
print(
color_text(f"Status change: {event.get('message', '')}", "green"),
old_status,
"->",
new_status,
)
current_status = new_status
## ...
JSON output
Copy
Ask AI
{
"cursor_id": "019c7d25-8290-7e4b-a094-1c880f73c534",
"results": [
{
"created_time": "2026-02-20T22:22:16.111319Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "SFRR",
"message": "SOV Fixer results were received",
"new_value": null,
"old_value": null,
"pingid": "p-mk-ourke-esw5ab",
"user_id": 28,
"uuid": "019c7d25-822f-72b6-a78c-0eafb4040206"
},
{
"created_time": "2026-02-20T22:22:16.208950Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "SSC",
"message": "Submission status was changed from Initializing to Pending Clearance",
"new_value": "019c6d26-4e1d-7b8a-bad1-c05ed808dc4d",
"old_value": "019c6d26-4e1f-763c-ba7e-b6149e693a23",
"pingid": "p-mk-ourke-esw5ab",
"user_id": 34,
"uuid": "019c7d25-8290-7e4b-a094-1c880f73c534"
},
"..."
]
}
4. Change Submission Status
The fourth API call to make is Change Submission Status. This endpoint allows the user to programmatically transition the submission through workflow stages by providing the submission ID and the targetworkflow_status_uuid.
E.g., https://vision.pingintel.com/api/v1/submission/pv-acme-abc123/change_status
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 4. Change Submission Status
# https://docs.pingintel.com/ping-vision/update-submission/change-submission-status
change_status_url = f"{BASE_URL}/submission/{submission_id}/change_status"
if current_status == status_uuids.get("Pending Clearance"):
# In a real workflow, the clearance team would evaluate the submission
# for factors like incumbents or duplicate submissions from other brokerages.
# For this example, we will just automatically clear it to demonstrate the workflow.
print(color_text("Changing status to Cleared...", "yellow"))
response = requests.patch(
change_status_url,
json={"workflow_status_uuid": status_uuids["Cleared"]},
headers=headers,
)
if response.status_code not in (200, 201):
raise RuntimeError(f"Failed to change status: {response.status_code}")
print(color_text("Status changed to: Cleared", "green"))
if current_status == status_uuids.get("Cleared"):
# In a real workflow, this transition happens when the DTI (Days To Inception) date
# is reached, or when manually requested to begin the scrubbing/data entry process.
# For this example, we will just automatically move it to Data Entry to demonstrate the workflow.
print(color_text("Changing status to Data Entry...", "yellow"))
response = requests.patch(
change_status_url,
json={"workflow_status_uuid": status_uuids["Data Entry"]},
headers=headers,
)
if response.status_code not in (200, 201):
raise RuntimeError(f"Failed to change status: {response.status_code}")
print(color_text("Status changed to: Data Entry", "green"))
break
time.sleep(POLL_INTERVAL)
current_status_name = next((k for k, v in status_uuids.items() if v == current_status), None)
print(f"Waiting... (current status: {color_text(current_status_name, 'yellow')})")
## ...
JSON output
Copy
Ask AI
{
"status": "success"
}
5. Download Scrubber for Human Certification
The fifth API call to make is List Recent Submission Activity. This endpoint retrieves submission details including thedocuments list. Use this to find and download the SOVFIXER_SCRUBBER document for human certification.
E.g., https://vision.pingintel.com/api/v1/submission?id=pv-acme-abc123
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 5. Download Scrubber for Human Certification
# https://docs.pingintel.com/ping-vision/get-submission-data/list-recent-submission-activity
print(color_text("\nPress enter to download the scrubber for certification.", "red"))
input("> ")
activity_params = {
"id": submission_id,
"page_size": 1,
"team_uuid": team_uuid,
"division_uuid": division_uuid,
}
activity_response = requests.get(submission_activity_url, params=activity_params, headers=headers)
if activity_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to get submission activity: {activity_response.status_code}")
activity_json = activity_response.json()
submission_data = activity_json.get("results", [])[0]
documents = submission_data.get("documents", [])
scrubber_doc = None
for doc in documents:
if doc.get("document_type") == "SOVFIXER_SCRUBBER":
scrubber_doc = doc
break
if not scrubber_doc:
raise RuntimeError("Scrubber document not found")
scrubber_filename = f"{submission_id}_{scrubber_doc['filename']}"
scrubber_response = requests.get(scrubber_doc["url"], headers=headers)
if scrubber_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to download scrubber: {scrubber_response.status_code}")
with open(scrubber_filename, "wb") as f:
f.write(scrubber_response.content)
print(color_text(f"Downloaded scrubber to: {scrubber_filename}", "green"))
print(
color_text(
"Open in Excel and certify the scrubber in the Ping menu, then return here.",
"yellow",
)
)
## ...
JSON output
Copy
Ask AI
{
"cursor_id": "p-mk-ourke-esw5ab",
"has_remaining": false,
"results": [
{
"actions": {
"claim": true,
"download_documents": true,
"transition_to": {
"019c6d26-4e1b-7c15-84d5-a3d5a3f7583f": "Delivered",
"019c6d26-4e1d-7b8a-bad1-c05ed808dc4d": "Pending Clearance",
"019c6d26-4e1f-763c-ba7e-b6149e693a23": "Initializing",
"019c6d26-4e24-73d5-a2f0-ac50f422ba51": "Underwriting",
"019c6d26-4e26-751a-ab3f-9ae93b0ce114": "Blocked",
"019c77df-548f-7628-952e-eee975776593": "Cleared"
},
"view_map": true
},
"automated_processing_failed": false,
"automated_processing_failed_reason": null,
"broker_assistant_name": null,
"...": "...",
"documents": [
{
"actions": ["download", "archive"],
"archived_on": null,
"archived_reason": null,
"created_time": "2026-02-20T22:21:22.216034Z",
"document_type": "MSG",
"filename": "demo_email.eml",
"id": 625,
"is_archived": false,
"label": "MSG",
"preview_url": null,
"url": "http://vision.pingintel.com/api/v1/submission/p-mk-ourke-esw5ab/document/demo_email.eml"
},
"..."
],
"is_submission_ping_certified": true,
"jobs": [
{
"created_time": "2026-02-20T22:21:26.655911Z",
"filenames": ["parse_sov_testfile.xlsx", "Email - 02-20-2026.content.html"],
"job_id": "7f3444de-0eaa-11f1-ba93-befce6b35b80",
"job_type": "SOVFIXER",
"job_type_details": {
"job_type_label": "Ping.Extraction",
"sovfixer_request_type": null,
"sovfixer_result_message": null,
"sovfixer_result_status": null,
"sovfixer_sovid": "s-mk-ourke-g1bnkd"
},
"processing_last_message": "Processing complete.",
"processing_pct_complete": 100.0,
"processing_status": "C",
"sovid": "s-mk-ourke-g1bnkd",
"updated_time": "2026-02-20T22:22:16.102475Z",
"user_id": 28
}
],
"...": "..."
}
]
}
6. Certify Scrubber in Excel
After downloading the scrubber, open it in Excel. Use the Ping menu in Excel to review the extracted data and certify the submission.7. Wait for Underwriting Status (Human Certification Complete)
After the human operator reviews and certifies the scrubber in Excel using the Ping menu, continue polling List Submission Events until the submission reaches the “Underwriting” status. To query on page size, team, and division, include those parameters as well. This URL will poll for events related to the provided submission, team, and division starting from February 20, 2026 (the date of this example submission):https://vision.pingintel.com/api/v1/submission-events?pingid={id}&start=20260220000000&page_size=50&team={team_uuid}&division={division_uuid}
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 7. Wait for Underwriting Status (Human Certification Complete)
print(color_text("\nWaiting for human certification (Underwriting status)...", "yellow"))
while True:
events_params = {
"pingid": submission_id,
"start": earliest_allowed_time.strftime("%Y%m%d%H%M%S"),
"page_size": 50,
"team": team_uuid,
"division": division_uuid,
}
if last_cursor_id:
events_params["cursor_id"] = last_cursor_id
events_response = requests.get(submission_events_url, params=events_params, headers=headers)
if events_response.status_code not in (200, 201):
print(color_text(f"Error polling events: {events_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
events_json = events_response.json()
last_cursor_id = events_json.get("cursor_id")
for event in events_json.get("results", []):
if event.get("event_type") == t.SUBMISSION_EVENT_LOG_TYPE.SUBMISSION_STATUS_CHANGE:
print(color_text(f"Status change: {event.get('message', '')}", "green"))
current_status = event.get("new_value", "")
if current_status == status_uuids.get("Underwriting"):
print(color_text("Certification complete. Underwriting status reached.", "green"))
break
time.sleep(POLL_INTERVAL)
current_status_name = next((k for k, v in status_uuids.items() if v == current_status), None)
print(f"Waiting for certification... (current status: {color_text(current_status_name or 'unknown', 'yellow')})")
## ...
JSON output
Copy
Ask AI
{
"cursor_id": "019c7d3e-dd84-74f1-a7a1-cc41ee75ad8e",
"results": [
{
"created_time": "2026-02-20T22:49:57.562391Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "SFUR",
"message": "SOV Fixer update received",
"new_value": null,
"old_value": null,
"pingid": "p-mk-ourke-esw5ab",
"user_id": 34,
"uuid": "019c7d3e-dc3a-7870-be37-c9ddedf31142"
},
{
"created_time": "2026-02-20T22:49:57.661324Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "SSC",
"message": "Submission status was changed from Data Entry to Underwriting",
"new_value": "019c6d26-4e24-73d5-a2f0-ac50f422ba51",
"old_value": "019c6d26-4e21-784d-bd26-9d7d70991d91",
"pingid": "p-mk-ourke-esw5ab",
"user_id": 34,
"uuid": "019c7d3e-dc9d-73a1-9d3f-241126fef6b6"
},
{
"created_time": "2026-02-20T22:49:57.758105Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "ROUT",
"message": "Ran outputters",
"new_value": null,
"old_value": null,
"pingid": "p-mk-ourke-esw5ab",
"user_id": 34,
"uuid": "019c7d3e-dcfe-79c0-bf51-5e4a76ef0462"
},
{
"created_time": "2026-02-20T22:49:57.892794Z",
"division_uuid": "12345678-abc1-cde2-efg3-123456789abcd",
"event_type": "SFUR",
"message": "SOV Fixer update received",
"new_value": null,
"old_value": null,
"pingid": "p-mk-ourke-esw5ab",
"user_id": 34,
"uuid": "019c7d3e-dd84-74f1-a7a1-cc41ee75ad8e"
}
]
}
8. Wait for the RUN_OUTPUTTERS Job to Complete
After certification, poll List Recent Submission Activity until theRUN_OUTPUTTERS job reaches 100% completion. This job generates the final output documents.
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 8. Wait for the RUN_OUTPUTTERS job to complete
print(color_text("Waiting for final outputs...", "yellow"))
while True:
activity_params = {
"id": submission_id,
"page_size": 1,
"team_uuid": team_uuid,
"division_uuid": division_uuid,
}
activity_response = requests.get(submission_activity_url, params=activity_params, headers=headers)
if activity_response.status_code not in (200, 201):
print(color_text(f"Error checking activity: {activity_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
results = activity_response.json().get("results", [])
if not results:
time.sleep(POLL_INTERVAL)
continue
submission = results[0]
jobs = submission.get("jobs", [])
outputters_job = next((j for j in jobs if j.get("job_type") == "RUN_OUTPUTTERS"), None)
if outputters_job and outputters_job.get("processing_pct_complete") == 100:
print(color_text("Final outputs ready for download.", "green"))
break
time.sleep(POLL_INTERVAL)
## ...
JSON output
Copy
Ask AI
{
"cursor_id": "p-mk-ourke-esw5ab",
"has_remaining": false,
"results": [
{
"actions": {
"claim": true,
"download_documents": true,
"transition_to": {
"019c6d26-4e1b-7c15-84d5-a3d5a3f7583f": "Delivered",
"019c6d26-4e1d-7b8a-bad1-c05ed808dc4d": "Pending Clearance",
"019c6d26-4e1f-763c-ba7e-b6149e693a23": "Initializing",
"019c6d26-4e24-73d5-a2f0-ac50f422ba51": "Underwriting",
"019c6d26-4e26-751a-ab3f-9ae93b0ce114": "Blocked",
"019c77df-548f-7628-952e-eee975776593": "Cleared"
},
"view_map": true
},
"automated_processing_failed": false,
"automated_processing_failed_reason": null,
"broker_assistant_name": null,
"...": "...",
"documents": [
{
"actions": ["download", "archive"],
"archived_on": null,
"archived_reason": null,
"created_time": "2026-02-20T22:21:22.216034Z",
"document_type": "MSG",
"filename": "demo_email.eml",
"id": 625,
"is_archived": false,
"label": "MSG",
"preview_url": null,
"url": "http://vision.pingintel.com/api/v1/submission/p-mk-ourke-esw5ab/document/demo_email.eml"
},
"..."
],
"is_submission_ping_certified": true,
"jobs": [
{
"created_time": "2026-02-20T22:21:26.655911Z",
"filenames": ["parse_sov_testfile.xlsx", "Email - 02-20-2026.content.html"],
"job_id": "7f3444de-0eaa-11f1-ba93-befce6b35b80",
"job_type": "SOVFIXER",
"job_type_details": {
"job_type_label": "Ping.Extraction",
"sovfixer_request_type": null,
"sovfixer_result_message": null,
"sovfixer_result_status": null,
"sovfixer_sovid": "s-mk-ourke-g1bnkd"
},
"processing_last_message": "Processing complete.",
"processing_pct_complete": 100.0,
"processing_status": "C",
"sovid": "s-mk-ourke-g1bnkd",
"updated_time": "2026-02-20T22:22:16.102475Z",
"user_id": 28
}
],
"...": "..."
}
]
}
9. Download Final Output Documents
The final API call to make is Download Submission Document. This endpoint allows the user to download the processed output documents by providing the submission ID and the document filename. Use thedocuments list from the polling response to identify which documents to download, filtering by document_type.
E.g., https://vision.pingintel.com/api/v1/submission/pv-acme-abc123/document/My Example SOV.json
Example code:
clear_and_triage.py
Copy
Ask AI
## ...
# 9. Download Final Output Documents
# https://docs.pingintel.com/ping-vision/get-submission-data/download-submission-document
os.makedirs("workflow_example_results", exist_ok=True)
for doc in submission.get("documents", []):
doc_type = doc.get("document_type", "")
filename = doc.get("filename", "")
if doc_type in ["SOVFIXER_OUTPUT", "SOVFIXER_JSON"] and not doc.get("is_archived", False):
download_url = f"{BASE_URL}/submission/{submission_id}/document/{filename}"
download_response = requests.get(download_url, headers=headers)
if download_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to download {filename}: {download_response.status_code}")
output_path = f"workflow_example_results/{submission_id}_{filename}"
with open(output_path, "wb") as outfile:
outfile.write(download_response.content)
print(color_text(f"Saved {doc_type}: {output_path}", "green"))
print(color_text(f"\nWorkflow complete for submission {submission_id}", "green"))
##
Python output
Copy
Ask AI
Saved SOVFIXER_OUTPUT: workflow_example_results/p-mk-ourke-esw5ab_Acme Corp 2026-02-PING.xlsm
Saved SOVFIXER_OUTPUT: workflow_example_results/p-mk-ourke-esw5ab_Ping Marketing SOV - Acme Corp.xlsx
Saved SOVFIXER_JSON: workflow_example_results/p-mk-ourke-esw5ab_Acme Corp 2026-02.json
Workflow complete for submission p-mk-ourke-esw5ab
Python Demo
Download Python Script here ||| Download Example Email hereclear_and_triage_api.py
Copy
Ask AI
import os
import time
from datetime import datetime, timezone
from pathlib import Path
import requests
from pingintel_api.pingvision import types as t
POLL_INTERVAL = 15 # seconds
def color_text(text: str, color: str) -> str:
"""Wrap text in ANSI color codes for terminal output."""
colors = {"red": "\033[31m", "yellow": "\033[33m", "green": "\033[32m"}
if color not in colors:
return text
reset = "\033[0m"
return f"{colors[color]}{text}{reset}"
# Configuration
API_KEY = os.environ.get("PINGVISION_AUTH_TOKEN")
BASE_URL = "https://vision.pingintel.com/api/v1"
headers = {"Authorization": f"Token {API_KEY}"}
# File to submit
file_path = Path("demo_email.eml")
files = [("files", (file_path.name, open(file_path, "rb")))]
## ...
# 1. List User Teams
# https://docs.pingintel.com/ping-vision/user-memberships/list-user-teams
list_teams_url = f"{BASE_URL}/user/teams/"
list_teams_response = requests.get(list_teams_url, headers=headers)
if list_teams_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to list teams: {list_teams_response.status_code}")
teams = list_teams_response.json()
team = teams[0] # Select team by index, or filter by team_name/company_name
team_uuid = team["team_uuid"]
division_uuid = team["division_uuid"]
status_uuids = {s["name"]: s["uuid"] for s in team.get("statuses", [])}
## ...
# 2. Create Submission
# https://docs.pingintel.com/ping-vision/create-submission/initiate-new-submission
payload = {
"client_ref": "my_salesforce_id", # Optional: external reference ID
"insured_name": "Acme Corp", # Optional: insured name
"team_uuid": team_uuid, # Required: team ID
}
create_submission_url = f"{BASE_URL}/submission"
create_submission_response = requests.post(create_submission_url, data=payload, files=files, headers=headers)
if create_submission_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to create submission: {create_submission_response.status_code}")
submission_id = create_submission_response.json()["id"]
print(f"Created submission: {color_text(submission_id, 'green')}")
## ...
# 3. Poll for Status Changes via List Submission Events
# https://docs.pingintel.com/ping-vision/get-submission-data/list-submission-events
earliest_allowed_time = datetime.now(timezone.utc)
last_cursor_id = None
current_status = None
submission_events_url = f"{BASE_URL}/submission-events"
submission_activity_url = f"{BASE_URL}/submission"
print(f"Polling for status changes on submission {color_text(submission_id, 'yellow')}...")
while True:
events_params = {
"pingid": submission_id,
"start": earliest_allowed_time.strftime("%Y%m%d%H%M%S"),
"page_size": 50,
"team": team_uuid,
"division": division_uuid,
}
if last_cursor_id:
events_params["cursor_id"] = last_cursor_id
events_response = requests.get(submission_events_url, params=events_params, headers=headers)
if events_response.status_code not in (200, 201):
print(color_text(f"Error polling events: {events_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
events_json = events_response.json()
last_cursor_id = events_json.get("cursor_id")
# Process status change events
for event in events_json.get("results", []):
if event.get("event_type") == t.SUBMISSION_EVENT_LOG_TYPE.SUBMISSION_STATUS_CHANGE:
old_status = event.get("old_value", "")
new_status = event.get("new_value", "")
print(
color_text(f"Status change: {event.get('message', '')}", "green"),
old_status,
"->",
new_status,
)
current_status = new_status
# 4. Change Submission Status
# https://docs.pingintel.com/ping-vision/update-submission/change-submission-status
change_status_url = f"{BASE_URL}/submission/{submission_id}/change_status"
if current_status == status_uuids.get("Pending Clearance"):
# In a real workflow, the clearance team would evaluate the submission
# for factors like incumbents or duplicate submissions from other brokerages.
# For this example, we will just automatically clear it to demonstrate the workflow.
print(color_text("Changing status to Cleared...", "yellow"))
response = requests.patch(
change_status_url,
json={"workflow_status_uuid": status_uuids["Cleared"]},
headers=headers,
)
if response.status_code not in (200, 201):
raise RuntimeError(f"Failed to change status: {response.status_code}")
print(color_text("Status changed to: Cleared", "green"))
if current_status == status_uuids.get("Cleared"):
# In a real workflow, this transition happens when the DTI (Days To Inception) date
# is reached, or when manually requested to begin the scrubbing/data entry process.
# For this example, we will just automatically move it to Data Entry to demonstrate the workflow.
print(color_text("Changing status to Data Entry...", "yellow"))
response = requests.patch(
change_status_url,
json={"workflow_status_uuid": status_uuids["Data Entry"]},
headers=headers,
)
if response.status_code not in (200, 201):
raise RuntimeError(f"Failed to change status: {response.status_code}")
print(color_text("Status changed to: Data Entry", "green"))
break
time.sleep(POLL_INTERVAL)
current_status_name = next((k for k, v in status_uuids.items() if v == current_status), None)
print(f"Waiting... (current status: {color_text(current_status_name, 'yellow')})")
## ...
# 5. Download Scrubber for Human Certification
# https://docs.pingintel.com/ping-vision/get-submission-data/list-recent-submission-activity
print(color_text("\nPress enter to download the scrubber for certification.", "red"))
input("> ")
activity_params = {
"id": submission_id,
"page_size": 1,
"team_uuid": team_uuid,
"division_uuid": division_uuid,
}
activity_response = requests.get(submission_activity_url, params=activity_params, headers=headers)
if activity_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to get submission activity: {activity_response.status_code}")
activity_json = activity_response.json()
submission_data = activity_json.get("results", [])[0]
documents = submission_data.get("documents", [])
scrubber_doc = None
for doc in documents:
if doc.get("document_type") == "SOVFIXER_SCRUBBER":
scrubber_doc = doc
break
if not scrubber_doc:
raise RuntimeError("Scrubber document not found")
scrubber_filename = f"{submission_id}_{scrubber_doc['filename']}"
scrubber_response = requests.get(scrubber_doc["url"], headers=headers)
if scrubber_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to download scrubber: {scrubber_response.status_code}")
with open(scrubber_filename, "wb") as f:
f.write(scrubber_response.content)
print(color_text(f"Downloaded scrubber to: {scrubber_filename}", "green"))
print(
color_text(
"Open in Excel and certify the scrubber in the Ping menu, then return here.",
"yellow",
)
)
## ...
# 6. After downloading the scrubber, open it in Excel. Use the Ping menu in Excel to review the extracted data and certify the submission.
## ...
# 7. Wait for Underwriting Status (Human Certification Complete)
print(color_text("\nWaiting for human certification (Underwriting status)...", "yellow"))
while True:
events_params = {
"pingid": submission_id,
"start": earliest_allowed_time.strftime("%Y%m%d%H%M%S"),
"page_size": 50,
"team": team_uuid,
"division": division_uuid,
}
if last_cursor_id:
events_params["cursor_id"] = last_cursor_id
events_response = requests.get(submission_events_url, params=events_params, headers=headers)
if events_response.status_code not in (200, 201):
print(color_text(f"Error polling events: {events_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
events_json = events_response.json()
last_cursor_id = events_json.get("cursor_id")
for event in events_json.get("results", []):
if event.get("event_type") == t.SUBMISSION_EVENT_LOG_TYPE.SUBMISSION_STATUS_CHANGE:
print(color_text(f"Status change: {event.get('message', '')}", "green"))
current_status = event.get("new_value", "")
if current_status == status_uuids.get("Underwriting"):
print(color_text("Certification complete. Underwriting status reached.", "green"))
break
time.sleep(POLL_INTERVAL)
current_status_name = next((k for k, v in status_uuids.items() if v == current_status), None)
print(f"Waiting for certification... (current status: {color_text(current_status_name or 'unknown', 'yellow')})")
## ...
# 8. Wait for the RUN_OUTPUTTERS job to complete
print(color_text("Waiting for final outputs...", "yellow"))
while True:
activity_params = {
"id": submission_id,
"page_size": 1,
"team_uuid": team_uuid,
"division_uuid": division_uuid,
}
activity_response = requests.get(submission_activity_url, params=activity_params, headers=headers)
if activity_response.status_code not in (200, 201):
print(color_text(f"Error checking activity: {activity_response.status_code}", "red"))
time.sleep(POLL_INTERVAL)
continue
results = activity_response.json().get("results", [])
if not results:
time.sleep(POLL_INTERVAL)
continue
submission = results[0]
jobs = submission.get("jobs", [])
outputters_job = next((j for j in jobs if j.get("job_type") == "RUN_OUTPUTTERS"), None)
if outputters_job and outputters_job.get("processing_pct_complete") == 100:
print(color_text("Final outputs ready for download.", "green"))
break
time.sleep(POLL_INTERVAL)
## ...
# 9. Download Final Output Documents
# https://docs.pingintel.com/ping-vision/get-submission-data/download-submission-document
os.makedirs("workflow_example_results", exist_ok=True)
for doc in submission.get("documents", []):
doc_type = doc.get("document_type", "")
filename = doc.get("filename", "")
if doc_type in ["SOVFIXER_OUTPUT", "SOVFIXER_JSON"] and not doc.get("is_archived", False):
download_url = f"{BASE_URL}/submission/{submission_id}/document/{filename}"
download_response = requests.get(download_url, headers=headers)
if download_response.status_code not in (200, 201):
raise RuntimeError(f"Failed to download {filename}: {download_response.status_code}")
output_path = f"workflow_example_results/{submission_id}_{filename}"
with open(output_path, "wb") as outfile:
outfile.write(download_response.content)
print(color_text(f"Saved {doc_type}: {output_path}", "green"))
print(color_text(f"\nWorkflow complete for submission {submission_id}", "green"))
##