Skip to main content
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, upload one or more location CSV files, start processing, and poll for completion. 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 instead.

0. Preamble

Set up authentication before starting the workflow. update_sov.py
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. 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
update_sov.py
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"]
Example response:
{
  "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. 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 at least once before starting the job, and repeat it to upload locations in batches. POST /api/v1/sov/update/{update_id}/add_locations
update_sov.py
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()
Example response:
{
  "message": "OK"
}
Example response (400 — invalid CSV attribute):
{
  "file": "sheet_row_numbah: unknown attribute"
}
Example response (409 — job is no longer pending):
{
  "message": "rejected locations file as SUD not pending"
}

3. Start SOV Update Job

The third API call to make is 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. The job processes asynchronously, and the response confirms it has been queued. POST /api/v1/sov/update/{update_id}/start
update_sov.py
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()
Example response:
{
  "message": "OK"
}

4. Check SOV Update Status

The fourth API call to make is 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}
update_sov.py
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", [])
Example response (in progress):
{
  "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):
{
  "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 one of FAILED_TO_READ, PARTIAL_PARSE, FAILED_TO_PARSE, or FAILED_TO_PROCESS, and result.message describes the error. Example response (failed):
{
  "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.
update_sov.py
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']}")

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.                  Download Python Script here ||| Download corrections.csv here
update_sov.py
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']}")