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

# Get or Create an SOV Output

> Example workflow for requesting an additional output format from an existing SOV via the **Ping.Extraction** API

Below describes an example workflow of API calls that can be made in order to retrieve an output for an SOV that has already been parsed. Reach for this workflow when one of the following is true:

* You need an output format that was not requested at parse time. The original [Start SOV Parsing Job](/ping-extraction/parse-sovs/start-sov-parsing-job) call accepts an `output_formats` list. Any format you did not include there will not exist until you request it here.
* You want to regenerate an existing output. Pass `overwrite_existing` as `true` to discard the cached file and produce a fresh one.
* You want to fetch an output for a specific revision of the SOV. Pass `revision` to target a specific revision: `0` for the initial SOV, a positive integer for that revision number, or `-1` (the default) for the latest.

Use this workflow instead of re-submitting the source file to [Start SOV Parsing Job](/ping-extraction/parse-sovs/start-sov-parsing-job): the SOV is already parsed, and this endpoint reuses that work. It returns a cached output immediately if one is available. Otherwise, it starts a new generation job and returns a request ID to poll.

This workflow uses two distinct IDs in two different shapes. Keep them separate:

* **SOV ID** — the ID of the parsed SOV (or SOV Update), returned by the original parsing job (e.g., `s-no-ping-hggcsk`). Pass this in the path of the POST.
* **Output request ID** — a UUID returned in the POST response under `request.id` (e.g., `9f4d8c10-3b7e-4a52-bd91-c823f15e4d76`). Pass this in the path of the GET when polling.

When the output is already cached, the request ID carries an `immediately-done-` prefix (for example, `immediately-done-7b3c1a04-2d6e-4f95-bf18-a08e25c4d731`). Treat the ID as opaque — use `request.status` to determine completion rather than parsing the prefix.

The sample Python script is available for download at the bottom of the page. Try filling in the blanks (e.g., \{sovid}, \{request\_id}) to go through the workflow using cURL!

### 1. Request the SOV Output

The first API call to make is [Get Or Create SOV Output](/ping-extraction/get-sov-data/get-or-create-sov-output).
Supply the SOV ID in the path and the desired `output_format` in the request body. If a matching output is already cached, the response returns `request.status` of `COMPLETE` and the result is available immediately — skip step 2 and go to step 3. Otherwise, the response returns a pending status and an `id` you will use to poll.

`output_format` accepts either lowercase or uppercase. If the supplied format is not enabled for your organization, the response is a `400` with a `message` listing the formats you may request. To discover the allowed values up front, call [List Available Output Formats](/ping-extraction/get-sov-data/list-available-output-formats).

Set `overwrite_existing` to `true` to force regeneration even when a cached output exists. Set `revision` to select a specific SOV revision. `-1` (the default) selects the latest revision and `0` selects the initial SOV. A positive integer selects that revision by number. If the SOV has no revision with that number, the response is a `404` with `{"detail": "Not found."}`. Revision is ignored when the path ID is a SUD ID.

E.g., `https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk/get_or_create_output`

**Example code:**

<CodeGroup dropdown>
  ```python get_or_create_output.py lines icon='python' theme={null}
  import time
  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}"}

  # SOV ID from a previously completed parsing job
  sovid = "s-no-ping-hggcsk"

  # request options
  payload = {
      # which output format do you want? e.g., json, auditor
      "output_format": "json",
      # latest revision (-1) or initial SOV (0)
      "revision": -1,
      # set to true to regenerate even if a cached output already exists
      "overwrite_existing": False,
  }

  # 1. API URL for Get Or Create SOV Output: https://api.sovfixer.com/api/v1/sov/{sovid}/get_or_create_output
  start_url = f"https://api.sovfixer.com/api/v1/sov/{sovid}/get_or_create_output"
  start_response = requests.post(start_url, json=payload, headers=headers)
  assert start_response.status_code in (200, 201)
  start_json = start_response.json()

  # the request.id is the polling ID for step 2 — it is NOT the SOV ID
  request_id = start_json["request"]["id"]
  status = start_json["request"]["status"]

  ## ...
  ```

  ```shell cURL output icon='square-terminal' theme={null}
      curl --request POST \
          --url "https://api.sovfixer.com/api/v1/sov/{sovid}/get_or_create_output" \
          --header "Authorization: Token <your_api_key_here>" \
          --header "Content-Type: application/json" \
          --data '{
              "output_format": "json",
              "revision": -1,
              "overwrite_existing": false
          }'
  ```
</CodeGroup>

**Example response (cached output already available):**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  {
      "request": {
          "id": "immediately-done-7b3c1a04-2d6e-4f95-bf18-a08e25c4d731",
          "status": "COMPLETE"
      },
      "result": {
          "label": "JSON",
          "output_format": "JSON",
          "scrubbed_filename": "parse_sov_testfile.json",
          "url": "https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk/output/parse_sov_testfile.json"
      }
  }
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
      "request": {
          "id": "immediately-done-7b3c1a04-2d6e-4f95-bf18-a08e25c4d731",
          "status": "COMPLETE"
      },
      "result": {
          "label": "JSON",
          "output_format": "JSON",
          "scrubbed_filename": "parse_sov_testfile.json",
          "url": "https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk/output/parse_sov_testfile.json"
      }
  }
  ```
</CodeGroup>

**Example response (generation started):**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  {
      "request": {
          "id": "9f4d8c10-3b7e-4a52-bd91-c823f15e4d76",
          "status": "PENDING"
      }
  }
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
      "request": {
          "id": "9f4d8c10-3b7e-4a52-bd91-c823f15e4d76",
          "status": "PENDING"
      }
  }
  ```
</CodeGroup>

### 2. Poll the SOV Output Request

The second API call to make is [Get/Check SOV Output Result](/ping-extraction/parse-sovs/getcheck-sov-output-result).
Skip this step if step 1 already returned `COMPLETE`. Otherwise, pass the `request.id` from step 1 in the path and poll until `request.status` is `COMPLETE` or `FAILED`. When the status is `COMPLETE`, `result` contains `label`, `output_format`, `scrubbed_filename`, and `url`. When the status is `FAILED`, `result.message` contains a description of the error.

E.g., `https://api.sovfixer.com/api/v1/sov/get_or_create_output/9f4d8c10-3b7e-4a52-bd91-c823f15e4d76`

**Example code:**

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

  # 2. API URL for Get/Check SOV Output Result: https://api.sovfixer.com/api/v1/sov/get_or_create_output/{request_id}
  check_url = f"https://api.sovfixer.com/api/v1/sov/get_or_create_output/{request_id}"
  check_json = start_json

  # Poll every three seconds for output generation
  while check_json["request"]["status"] not in ("COMPLETE", "FAILED"):
      print(check_json)
      print("Waiting 3 seconds to request again...")
      time.sleep(3)
      check_response = requests.get(check_url, headers=headers)
      assert check_response.status_code in (200, 201)
      check_json = check_response.json()

  print(check_json)
  assert check_json["request"]["status"] != "FAILED", "Output generation failed"

  result = check_json["result"]

  ## ...
  ```

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

**Example response:**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  {
      "request": {
          "status": "COMPLETE"
      },
      "result": {
          "label": "JSON",
          "output_format": "JSON",
          "scrubbed_filename": "parse_sov_testfile.json",
          "url": "https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk/output/parse_sov_testfile.json"
      }
  }
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  {
      "request": {
          "status": "COMPLETE"
      },
      "result": {
          "label": "JSON",
          "output_format": "JSON",
          "scrubbed_filename": "parse_sov_testfile.json",
          "url": "https://api.sovfixer.com/api/v1/sov/s-no-ping-hggcsk/output/parse_sov_testfile.json"
      }
  }
  ```
</CodeGroup>

### 3. Download the Output

Download the file directly from the `result.url` returned by the completed request. Save the response body as binary. The URL serves the file in its native format (e.g., JSON, XLSX).

**Example code:**

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

  # 3. Download the output from result.url
  download_response = requests.get(result["url"], headers=headers)
  assert download_response.status_code in (200, 201)

  output_path = f"workflow_example_results/{result['scrubbed_filename']}"
  os.makedirs(os.path.dirname(output_path), exist_ok=True)
  with open(output_path, "wb") as outfile:
      outfile.write(download_response.content)
  print(f"saved {result['scrubbed_filename']}")
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  curl --request GET \
      --url "<result.url from step 2>" \
      --header "Authorization: Token <your_api_key_here>" \
      --output "<result.scrubbed_filename from step 2>"
  ```
</CodeGroup>

**Example response:**

<CodeGroup dropdown>
  ```python Python output lines icon='python' theme={null}
  saved parse_sov_testfile.json
  ```

  ```shell cURL output icon='square-terminal' theme={null}
  # file written to parse_sov_testfile.json
  ```
</CodeGroup>

### Python Demo

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

<CodeGroup dropdown>
  ```python get_or_create_sov_output.py lines icon='python' theme={null}
  """Demo: request and download an output for an existing SOV.

  Two IDs are involved. Keep them separate:
    - SOVID: identifies the parsed SOV, passed in the POST path.
    - request_id: returned by the POST under request.id, used to poll the GET.
  """

  import os
  import time
  import requests


  BASE_URL = "https://api.sovfixer.com/api/v1"
  SOVID = "s-no-ping-hggcsk"   # SOV ID from a previously completed parsing job
  OUTPUT_FORMAT = "json"       # desired output format, e.g., json or auditor
  POLL_SECONDS = 3

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

  # 1. Request the output. If a cached output is available the response
  #    returns COMPLETE immediately. Otherwise, generation begins and we
  #    receive a request.id to poll.

  start_url = f"{BASE_URL}/sov/{SOVID}/get_or_create_output"
  payload = {"output_format": OUTPUT_FORMAT, "revision": -1, "overwrite_existing": False}
  start_json = requests.post(start_url, json=payload, headers=headers).json()
  request_id = start_json["request"]["id"]


  # 2. Poll until the output is ready. request_id is the polling ID — not SOVID.
  check_url = f"{BASE_URL}/sov/get_or_create_output/{request_id}"
  check_json = start_json
  while check_json["request"]["status"] not in ("COMPLETE", "FAILED"):
      time.sleep(POLL_SECONDS)
      check_json = requests.get(check_url, headers=headers).json()

  if check_json["request"]["status"] == "FAILED":
      raise RuntimeError(f"Output generation failed: {check_json}")

  # 3. Download the file. result.url serves the output in its native format,
  #    so write the response body as binary.
  result = check_json["result"]
  download = requests.get(result["url"], headers=headers)
  os.makedirs("workflow_example_results", exist_ok=True)
  with open(f"workflow_example_results/{result['scrubbed_filename']}", "wb") as f:
      f.write(download.content)
  print(f"saved {result['scrubbed_filename']}")
  ```
</CodeGroup>
