> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pingintel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Process an SOV

> Example workflow for submitting an SOV via the **Ping.Extraction** API

Below describes an example workflow of API calls that can be made in order to process an SOV and retrieve results using the **Ping.Extraction** API. Reach for this workflow when one of the following is true:

* You have a new Excel SOV that needs to be parsed for the first time.
* You need structured building data — addresses, limits, construction details, custom columns — extracted from the workbook.
* You want to request one or more output formats up front (e.g., `json`, `auditor`) and have them generated in a single job.
* You want third-party data integrations (e.g., Ping Geocoding `PG`, Ping Hazard `PH`) applied at parse time so they appear in the output.

If the SOV is already parsed and you only need a different output format or a regenerated copy, use [Get or Create an SOV Output](/workflows/ping-extraction/get-or-create-output) instead — it reuses the parsed result rather than re-parsing the source file.

If the SOV is already parsed and you need to update building attributes or regenerate outputs
with revised data, see [Update an SOV](/workflows/ping-extraction/update-sov).

The following is a specific example, there are more options available to the user that are documented in the Parse SOVs pages under **Ping.Extraction**.
The code blocks allow the user to select from Python or cURL (via terminal) use cases.

If you have already parsed an SOV and need to apply corrections or refresh enrichments, see [Update an SOV](/workflows/ping-extraction/update-sov) instead.

The sample SOV and Python script are both available for download at the bottom of the page. Try filling in the blanks (e.g., \{id}) to go through the workflow using cURL!

### 1. Start SOV Parsing Job

The first API call to make is [Start SOV Parsing Job](/ping-extraction/parse-sovs/start-sov-parsing-job).
This endpoint allows the user to submit an Excel file to **Ping** servers to be parsed as an SOV.

To see which `output_formats` your organization can request, call [List Available Output Formats](/ping-extraction/get-sov-data/list-available-output-formats). The `integrations` field accepts datasource codes (e.g., `PG`, `PH`) from [List Datasources](/ping-data/usage/list-datasources), which returns the datasources your account has access to. Supplying a format or integration not enabled for your organization causes the request to fail.

**Example code:**

<CodeGroup dropdown>
  ```python parse_sov.py lines icon='python' theme={null}
  import time
  from pathlib import Path
  import requests
  import os

  # authentication token that allows you to make requests to the API
  API_KEY=os.environ.get('SOVFIXER_AUTH_TOKEN')
  headers = {"Authorization": f"Token {API_KEY}"}

  # file being submitted
  file_path = Path("parse_sov_testfile.xlsx")
  files = {"file": ("parse_sov_testfile.xlsx", open(file_path, "rb"))}
  # request options
  payload = {
      # what kind of document is being processed? SOV
      "document_type": "SOV",
      # how should the file be outputted? json, auditor
      "output_formats": ["json", "auditor"],
      # what data integrations should be used? Ping Geocoding, Ping Hazard
      "integrations": ["PG", "PH"]
  }

  # 1. API URL for Start SOV Parsing Job: https://api.sovfixer.com/api/v1/sov
  start_job_url = f"https://api.sovfixer.com/api/v1/sov"
  # API response
  start_job_response = requests.post(start_job_url, data=payload, files=files, headers=headers)
  # check response status and record the SOV ID
  if start_job_response.status_code in (200, 201):
      sovid = start_job_response.json()["id"]
  else:
      raise RuntimeError

  ## ... 
  ```

  ```shell cURL output icon='square-terminal' theme={null}
      curl --request POST \
          --url "https://api.sovfixer.com/api/v1/sov" \
          --header "Authorization: Token <your_api_key_here>" \
          --header "Content-Type: multipart/form-data" \
          --form "document_type=SOV" \
          --form "output_formats=JSON" \
          --form "output_formats=AUDITOR" \
          --form 'integrations=PH' \
          --form 'integrations=PG' \
          --form "file=@parse_sov_testfile.xlsx"
  ```
</CodeGroup>

**Example response:**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  {'id': 's-pl-ping-21nyms3', 'message': 'OK'}
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
      "message": "OK",
      "id": "s-pl-ping-21nyms3"
  }
  ```
</CodeGroup>

### 2. Check SOV Parsing Status

The second API call to make is [Check SOV Parsing Job](/ping-extraction/parse-sovs/check-sov-parsing-status).
This endpoint allows the user to check the status of the SOV parsing job by providing the SOV ID generated in step 1 into the
API path parameter.

E.g., `https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3`

**Example code:**

<CodeGroup dropdown>
  ```python parse_sov.py lines icon='python' theme={null}
  ## ... 

  # 2. API URL for Check SOV Parsing Job: https://api.sovfixer.com/api/v1/sov/{id}
  check_job_url = f"https://api.sovfixer.com/api/v1/sov/{sovid}"
  check_job_response = requests.get(check_job_url, headers=headers)
  # ensure response is in a good state
  assert check_job_response.status_code in (200, 201)
  check_job_json = check_job_response.json()
  # Poll every three seconds for job completion
  while check_job_json["request"]["status"] not in ("COMPLETE", "FAILED"):
      print(check_job_json)
      print("Waiting 3 seconds to request again...")
      time.sleep(3)
      check_job_response = requests.get(check_job_url, headers=headers)
      # ensure response is in a good state
      assert check_job_response.status_code in (200, 201)
      check_job_json = check_job_response.json()

  print(check_job_json)
  assert check_job_json["request"]["status"] != "FAILED", "SOV parsing job failed"

  # record filenames and urls of the processed SOV outputs
  outputs = []
  for output in check_job_json["result"]["outputs"]:
      outputs.append(
              {
                  "filename": output["filename"], 
                  "url": output["url"]
              }
          )

  ## ...
  ```

  ```shell cURL output icon='square-terminal' theme={null}
      curl --request GET \
          --url https://api.sovfixer.com/api/v1/sov/{id} \
          --header 'Authorization: Token <your_api_key_here>'
  ```
</CodeGroup>

**Example response:**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  {
     "request":{
        "completed_at":"2026-01-13T17: 00: 05.982Z",
        "last_health_check_time":"2026-01-13T17: 00: 06.498Z",
        "last_health_status":"Processing completed, storing internal artifacts.",
        "pct_complete":100,
        "progress_started_at":"2026-01-13T16: 59: 59.443Z",
        "requested_at":"2026-01-13T16: 59: 58.841Z",
        "status":"COMPLETE"
     },
     "result":{
        "inputs":[
           {
              "filename":"parse_sov_testfile.xlsx",
              "identified_document_type":"SOV",
              "status":"SUCCESS",
              "status_message":"Parsed.",
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/input/parse_sov_testfile.xlsx"
           }
        ],
        "message":"Success.",
        "outputs":[
           {
              "description":"JSON",
              "filename":"parse_sov_testfile.json",
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/output/parse_sov_testfile.json"
           },
           {
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/output/parse_sov_testfile-Auditor.xlsx",
              "filename":"parse_sov_testfile-Auditor.xlsx",
              "description":"Ping SOV"
           }
        ],
        "status":"SUCCESS"
     }
  }
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
     "request":{
        "status":"COMPLETE",
        "requested_at":"2026-01-13T16:36:39.015Z",
        "progress_started_at":"2026-01-13T16:36:39.415Z",
        "completed_at":"2026-01-13T16:36:45.922Z",
        "pct_complete":100,
        "last_health_status":"Processing completed, storing internal artifacts.",
        "last_health_check_time":"2026-01-13T16:36:46.351Z"
     },
     "result":{
        "status":"SUCCESS",
        "message":"Success.",
        "inputs":[
           {
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/input/parse_sov_testfile.xlsx",
              "status":"SUCCESS",
              "filename":"parse_sov_testfile.xlsx",
              "status_message":"Parsed.",
              "identified_document_type":"SOV"
           }
        ],
        "outputs":[
           {
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/output/parse_sov_testfile.json",
              "filename":"parse_sov_testfile.json",
              "description":"JSON"
           },
           {
              "url":"https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/output/parse_sov_testfile-Auditor.xlsx",
              "filename":"parse_sov_testfile-Auditor.xlsx",
              "description":"Ping SOV"
           }
        ]
     }
  }
  ```
</CodeGroup>

### 3. Fetch Outputs of SOV Parsing Job

The third API call to make is [Fetch Outputs of SOV Parsing Job](/ping-extraction/parse-sovs/fetch-outputs-of-sov-parsing-job).
This endpoint allows the user to receive the contents of the completed SOV parsing job by providing the SOV ID generated
in step 1 and the output filename generated in step 2 into the
API path parameter.

E.g., `https://api.sovfixer.com/api/v1/sov/s-pl-ping-21nyms3/output/parse_sov_testfile.json`

**Example code:**

<CodeGroup dropdown>
  ```python parse_sov.py lines icon='python' theme={null}
  ## ...
  # 3. API URL for Fetch Outputs of SOV Parsing Job: https://api.sovfixer.com/api/v1/sov/{id}/output/{filename}
  for output in outputs:
      fetch_outputs_response = requests.get(output["url"], headers=headers)
      # ensure response is in a good state
      assert fetch_outputs_response.status_code in (200, 201)
      print(f"saving {output['filename']}")
      output_path = f"workflow_example_results/{output['filename']}"
      os.makedirs(os.path.dirname(output_path), exist_ok=True)
      with open(output_path, "wb") as outfile:
          outfile.write(fetch_outputs_response.content)    
      print(f"saved {output['filename']}")
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  # get .json file
  curl --request GET \
      --url "https://api.sovfixer.com/api/v1/sov/{id}/output/parse_sov_testfile.json" \
      --header "Authorization: Token <your_api_key_token>" \
      --output parse_sov_testfile.json \
      && jq . parse_sov_testfile.json | head -n 4

  # get .xlsx file
  curl --request GET \
      --url "https://api.sovfixer.com/api/v1/sov/{id}/output/parse_sov_testfile-Auditor.xlsx" \
      --header "Authorization: Token <your_api_key_token>" \
      --output parse_sov_testfile-Auditor.xlsx
  ```
</CodeGroup>

**Example response:**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  saving parse_sov_testfile.json
  'id': 's-pl-ping-3yskss'
  'source_filename': 'parse_sov_testfile.xlsx'
  'num_buildings': 311
  saved parse_sov_testfile.json
  saving parse_sov_testfile-Auditor.xlsx
  saved parse_sov_testfile-Auditor.xlsx
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
      "id": "s-pl-ping-21nyms3",
      "source_filename": "parse_sov_testfile.xlsx",
      "num_buildings": 311,
  # ...   
  ```
</CodeGroup>

### Python Demo

                    
<a href="https://raw.githubusercontent.com/pingintel/pingintel-api/main/examples/ping-extraction/parse_sov.py" download>Download Python Script here</a>
**|||**
<a href="https://raw.githubusercontent.com/pingintel/pingintel-api/main/examples/data/parse_sov_testfile.xlsx" download>Download Example SOV here</a>

<CodeGroup dropdown>
  ```python parse_sov.py lines icon='python' theme={null}
  import json
  import time
  from pathlib import Path
  import requests
  import os

  # authentication token that allows you to make requests to the API
  API_KEY=os.environ.get('SOVFIXER_AUTH_TOKEN')
  headers = {"Authorization": f"Token {API_KEY}"}

  # file being submitted
  file_path = Path("parse_sov_testfile.xlsx")
  files = {"file": ("parse_sov_testfile.xlsx", open(file_path, "rb"))}
  # request options
  payload = {
      # what kind of document is being processed? SOV
      "document_type": "SOV",
      # how should the file be outputted? json, auditor
      "output_formats": ["json", "auditor"],
      # what data integrations should be used? Ping Geocoding, Ping Hazard
      "integrations": ["PG", "PH"]
  }

  # 1. API URL for Start SOV Parsing Job: https://api.sovfixer.com/api/v1/sov
  start_job_url = f"https://api.sovfixer.com/api/v1/sov"
  # API response
  start_job_response = requests.post(start_job_url, data=payload, files=files, headers=headers)
  # check response status and record the SOV ID
  if start_job_response.ok is True:
      sovid = start_job_response.json()["id"]
  else:
      raise ValueError

  ## ... 

  # 2. API URL for Check SOV Parsing Job: https://api.sovfixer.com/api/v1/sov/{id}
  check_job_url = f"https://api.sovfixer.com/api/v1/sov/{sovid}"
  check_job_response = requests.get(check_job_url, headers=headers).json()
  # Poll every three seconds for job completion
  while check_job_response["request"]["status"] not in ("COMPLETE", "FAILED"):
      print(check_job_response)
      print("Waiting 3 seconds to request again...")
      time.sleep(3)
      check_job_response = requests.get(check_job_url, headers=headers).json()

  print(check_job_response)

  if check_job_response["request"]["status"] == "FAILED":
      raise NotImplementedError

  # record the outputteds filename of the processed SOV
  output_filenames = []
  for output in check_job_response["result"]["outputs"]:
      output_filenames.append(output["filename"])

  ## ...
  # 3. API URL for Fetch Outputs of SOV Parsing Job: https://api.sovfixer.com/api/v1/sov/{id}/output/{filename}
  for output_filename in output_filenames:
      fetch_outputs_url = f"https://api.sovfixer.com/api/v1/sov/{sovid}/output/{output_filename}"
      fetch_outputs_response = requests.get(fetch_outputs_url, headers=headers)

      print(f"saving {output_filename}")
      if output_filename.endswith("json"):
          with open(f"workflow_example_results/{output_filename}", "w") as outfile:
              json.dump(fetch_outputs_response.json(), outfile, indent=4)
          for k, v in list(fetch_outputs_response.json().items())[:4]:
              print(f"{k!r}: {v!r}")
      elif output_filename.endswith("xlsx"):
          with open(f"workflow_example_results/{output_filename}", "wb") as outfile:
              outfile.write(fetch_outputs_response.content)            
      else:
          NotImplementedError
      print(f"saved {output_filename}")

  ```
</CodeGroup>
