> ## 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.

# Update an SOV

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

Update an existing SOV by uploading a CSV of revised building attributes, starting the update job,
and downloading the regenerated outputs. The workflow calls four endpoints in sequence:
[initiate an update job](/ping-extraction/update-sovs/initiate-sov-update-job),
[upload one or more location CSV files](/ping-extraction/update-sovs/add-locations-to-sov-update-job),
[start processing](/ping-extraction/update-sovs/start-sov-update-job),
and [poll for completion](/ping-extraction/update-sovs/check-sov-update-status).

**Reach for this workflow when:**

* You have corrected or revised building attributes for an existing SOV
  and need regenerated outputs.
* You want to trigger a scrubber reoutput (`SCRUB`) without re-uploading the original source file.
* You need to refresh third-party data enrichments on a subset of buildings.
* You are building a pipeline that applies upstream data changes back to a processed SOV.

This workflow assumes the source file has already been parsed and you have a `sovid` from that
earlier job. If you need to parse a new SOV for the first time,
see [Process an SOV](/workflows/ping-extraction/parse-sov) instead. To run an update as one
stage of a larger loop that also submits parsing jobs and tracks every revision through
history polling, see [Build an SOV Pipeline](/workflows/ping-extraction/build-sov-pipeline).

### 0. Preamble

Set up authentication before starting the workflow.

**update\_sov.py**

```python theme={null}
import os
import time

import requests

API_KEY = os.environ.get("SOVFIXER_AUTH_TOKEN")
headers = {"Authorization": f"Token {API_KEY}"}

sovid = "s-no-ping-hggcsk"
```

### 1. Initiate SOV Update Job

The first API call to make is
[Initiate SOV Update Job](/ping-extraction/update-sovs/initiate-sov-update-job).
This endpoint creates an update job tied to an existing SOV. Supply the `sovid` path parameter.
`update_type` is optional and defaults to `API` if omitted. Pass `SCRUB` to trigger a scrubber
reoutput. You can also pass an optional `callback_url` for webhook delivery and a `client_ref`
for your own records. Save the returned `id`. Every subsequent call in this workflow uses it.
The linked reference page enumerates all available `update_type` values.

`POST /api/v1/sov/{sovid}/initiate_update`

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  resp = requests.post(
      f"https://api.sovfixer.com/api/v1/sov/{sovid}/initiate_update",
      headers=headers,
  )
  print(resp.json())
  resp.raise_for_status()
  update_id = resp.json()["id"]
  ```

  ```shell cURL icon='square-terminal' theme={null}
  curl --request POST \
    --url https://api.sovfixer.com/api/v1/sov/{sovid}/initiate_update \
    --header 'Authorization: Token <token>' \
    --header 'Content-Type: application/json' \
  ```
</CodeGroup>

Example response:

```json theme={null}
{
  "message": "OK",
  "id": "s-no-ping-hggcsk-r001"
}
```

### 2. Add Locations to SOV Update Job

The second API call to make is
[Add Locations to SOV Update Job](/ping-extraction/update-sovs/add-locations-to-sov-update-job).
Upload a CSV containing the building attributes you want to update. Each row must identify the
target building using `item_key` or both `sheet_name` and `sheet_row_number`. Call this endpoint
before starting the job, and repeat it to upload locations in batches. For a `SCRUB`
reoutput with no data changes, skip this step and start the job directly.

`POST /api/v1/sov/update/{update_id}/add_locations`

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  with open("corrections.csv", "rb") as f:
      resp = requests.post(
          f"https://api.sovfixer.com/api/v1/sov/update/{update_id}/add_locations",
          files={"file": ("corrections.csv", f)},
          headers=headers,
      )
  print(resp.json())
  resp.raise_for_status()
  ```

  ```shell cURL icon='square-terminal' theme={null}
  curl --request POST \
    --url https://api.sovfixer.com/api/v1/sov/update/{update_id}/add_locations \
    --header 'Authorization: Token <token>' \
    --form file='@corrections.csv'
  ```
</CodeGroup>

Example response:

```json theme={null}
{
  "message": "OK"
}
```

Example response (400 — invalid CSV attribute):

```json theme={null}
{
  "file": "sheet_row_numbah: unknown attribute"
}
```

Example response (409 — job is no longer pending):

```json theme={null}
{
  "message": "rejected locations file as SUD not pending"
}
```

### 3. Start SOV Update Job

The third API call to make is
[Start SOV Update Job](/ping-extraction/update-sovs/start-sov-update-job).
Once all locations are uploaded, start the job. `extra_data` is optional and defaults to an
empty object, but its values drive the output filename — pass fields like `insured_name` when
you have them. Specify the output formats you want generated. To see which `output_formats`
your organization can request, call
[List Available Output Formats](/ping-extraction/get-sov-data/list-available-output-formats).
The job processes asynchronously, and the response confirms it has been queued.

`POST /api/v1/sov/update/{update_id}/start`

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  resp = requests.post(
      f"https://api.sovfixer.com/api/v1/sov/update/{update_id}/start",
      json={
          "extra_data": {"insured_name": "Acme Corp"},
          "output_formats": ["json"],
      },
      headers=headers,
  )
  print(resp.json())
  resp.raise_for_status()
  ```

  ```shell cURL icon='square-terminal' theme={null}
  curl --request POST \
    --url https://api.sovfixer.com/api/v1/sov/update/{update_id}/start \
    --header 'Authorization: Token <token>' \
    --header 'Content-Type: application/json' \
    --data '{"extra_data": {"insured_name": "Acme Corp"}, "output_formats": ["json"]}'
  ```
</CodeGroup>

Example response:

```json theme={null}
{
  "message": "OK"
}
```

### 4. Check SOV Update Status

The fourth API call to make is
[Check SOV Update Status](/ping-extraction/update-sovs/check-sov-update-status).
Poll this endpoint until `request.status` is `COMPLETE` or `FAILED`. The `result` key is absent
from the response until the job completes. When complete, `result.outputs` lists the generated
files available for download.

`GET /api/v1/sov/update/{update_id}`

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  status_url = f"https://api.sovfixer.com/api/v1/sov/update/{update_id}"

  response_json = requests.get(status_url, headers=headers).json()
  while response_json["request"]["status"] not in ("COMPLETE", "FAILED"):
      print(response_json)
      print("Waiting 3 seconds to request again...")
      time.sleep(3)
      response_json = requests.get(status_url, headers=headers).json()

  print(response_json)
  outputs = response_json.get("result", {}).get("outputs", [])
  ```

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

Example response (in progress):

```json theme={null}
{
  "request": {
    "request_uuid": "019e7b2ac9137c5d8e32dd64ef953b21",
    "status": "IN_PROGRESS",
    "completed_at": null,
    "pct_complete": 30,
    "last_health_status": "Priming enrichment cache.",
    "last_health_check_time": "2026-06-03T22:15:57.590Z",
    "sudid": "s-no-ping-hggcsk-r001",
    "record_type": "SCRUB"
  }
}
```

Example response (complete):

```json theme={null}
{
  "request": {
    "request_uuid": "019e7b2ac9137c5d8e32dd64ef953b21",
    "status": "COMPLETE",
    "completed_at": "2026-06-03T22:16:01.393Z",
    "pct_complete": 100,
    "last_health_status": "Processing completed, storing internal artifacts.",
    "last_health_check_time": "2026-06-03T22:16:01.398Z",
    "sudid": "s-no-ping-hggcsk-r001",
    "record_type": "SCRUB"
  },
  "result": {
    "status": "SUCCESS",
    "message": "Success.",
    "outputs": [
      {
        "description": "JSON",
        "filename": "Acme Corp 2026-06.json",
        "url": "https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk-r001/output/Acme%20Corp%202026-06.json"
      }
    ]
  }
}
```

`result` is absent until `request.status` reaches `COMPLETE` or `FAILED`. On failure,
`result.status` is `FAILED_TO_PARSE` or `FAILED_TO_PROCESS`, and `result.message`
describes the error.

Example response (failed):

```json theme={null}
{
  "request": {
    "request_uuid": "019e7b2ac9137c5d8e32dd64ef953b21",
    "status": "FAILED",
    "completed_at": "2026-06-03T22:16:01.393Z",
    "pct_complete": 100,
    "last_health_status": "Processing failed.",
    "last_health_check_time": "2026-06-03T22:16:01.398Z",
    "sudid": "s-no-ping-hggcsk-r001",
    "record_type": "SCRUB"
  },
  "result": {
    "status": "FAILED_TO_PROCESS",
    "message": "<error detail>"
  }
}
```

### 5. Fetch Outputs

Download each file listed in `result.outputs`.
Use the `filename` to name the saved file locally.

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  for out in outputs:
      print(f"saving {out['filename']}")
      r = requests.get(out["url"], headers=headers)
      r.raise_for_status()
      with open(out["filename"], "wb") as f:
          f.write(r.content)
      print(f"saved {out['filename']}")
  ```

  ```shell cURL icon='square-terminal' theme={null}
  curl --request GET \
    --url '<result.outputs[].url>' \
    --header 'Authorization: Token <token>' \
    --output '<result.outputs[].filename>'
  ```
</CodeGroup>

### Python Demo

A runnable script that initiates the update job, uploads `corrections.csv`, starts processing,
polls for completion, and downloads each generated output. Set `SOVFIXER_AUTH_TOKEN` and place
`corrections.csv` in the working directory, then run with `python update_sov.py`.

                
<a href="https://raw.githubusercontent.com/pingintel/pingintel-api/main/examples/ping-extraction/update_sov.py" download>Download Python Script here</a>
|||
<a href="https://raw.githubusercontent.com/pingintel/pingintel-api/main/examples/data/corrections.csv" download>Download corrections.csv here</a>

<CodeGroup dropdown>
  ```python update_sov.py lines icon='python' theme={null}
  import os
  import time

  import requests

  API_KEY = os.environ.get("SOVFIXER_AUTH_TOKEN")
  BASE_URL = "https://api.sovfixer.com/api/v1"

  SOVID = "s-no-ping-hggcsk"
  LOCATIONS_CSV = "corrections.csv"
  EXTRA_DATA = {"insured_name": "Acme Corp"}  # policy-level fields to carry into output
  OUTPUT_FORMATS = ["json"]
  POLL_SECONDS = 3

  headers = {"Authorization": f"Token {API_KEY}"}


  # 1. Initiate the update job
  resp = requests.post(
      f"{BASE_URL}/sov/{SOVID}/initiate_update",
      headers=headers,
  )
  print(resp.json())
  resp.raise_for_status()
  UPDATE_ID = resp.json()["id"]
  print("initiated update job:", UPDATE_ID)


  # 2. Add locations from CSV
  with open(LOCATIONS_CSV, "rb") as f:
      resp = requests.post(
          f"{BASE_URL}/sov/update/{UPDATE_ID}/add_locations",
          files={"file": (LOCATIONS_CSV, f)},
          headers=headers,
      )
  print(resp.json())
  resp.raise_for_status()


  # 3. Start the job. extra_data is optional but drives the output filename
  resp = requests.post(
      f"{BASE_URL}/sov/update/{UPDATE_ID}/start",
      json={"extra_data": EXTRA_DATA, "output_formats": OUTPUT_FORMATS},
      headers=headers,
  )
  print(resp.json())
  resp.raise_for_status()


  # 4. Poll until the job reaches a terminal state
  status_url = f"{BASE_URL}/sov/update/{UPDATE_ID}"
  response_json = requests.get(status_url, headers=headers).json()

  while response_json["request"]["status"] not in ("COMPLETE", "FAILED"):
      print(response_json)
      print("Waiting 3 seconds to request again...")
      time.sleep(POLL_SECONDS)
      response_json = requests.get(status_url, headers=headers).json()

  print(response_json)
  print("final status:", response_json["request"]["status"])


  # 5. Download outputs
  for out in response_json.get("result", {}).get("outputs", []):
      print(f"saving {out['filename']}")
      r = requests.get(out["url"], headers=headers)
      r.raise_for_status()
      with open(out["filename"], "wb") as f:
          f.write(r.content)
      print(f"saved {out['filename']}")
  ```
</CodeGroup>
