Skip to main content

Run a custom SNAP graph through the NSIS Cloud API

SNAP stands for Sentinel Application Platform. It is software for opening, viewing and processing satellite images. A SNAP graph is an XML file containing a saved sequence of processing instructions. It specifies which data to read, which operations to perform and where to save the result.

This example uses a Python script to send a graph to NSIS Cloud for processing. The supplied graph takes a small area from a Sentinel-2 image and saves it as a GeoTIFF, a TIFF file that includes geographic location information. The script downloads the result from your private S3 bucket. You then create a PNG preview that opens in an ordinary image viewer.

Prerequisites

No. 1 NSIS account and authentication

An active NSIS account and a current ACCESS_TOKEN are required to submit a processing order. For the general authentication and download procedure, see Using curl and wget to download EODATA products from NSIS Cloud.

Obtain your token using the sign-in instructions in Downloading Polish Sentinel-2 data with curl and wget on NSIS Cloud. Keep using the terminal where the token is set. This SNAP example reads its input product directly from the catalogue.

No. 2 S3 bucket and OpenStack credentials

Prepare a private S3 bucket in your NSIS Cloud OpenStack project. The bucket will store the XML graph and the processing result.

First obtain an S3 access key and secret key following How to generate and manage EC2 credentials on NSIS Cloud. Then create a bucket using How to access object storage from NSIS Cloud using boto3. Run the bucket-listing example in that article to confirm that the bucket is accessible through the S3 endpoint. If you already have a suitable bucket, use it for this example.

Keep the bucket name, HTTPS S3 endpoint, access key and secret key ready for Step 2. The graph and result use the same bucket, endpoint and keys. These Object Storage credentials are separate from EODATA access credentials.

Private nsis container in Horizon with graphs and snap-results folders

The screenshot shows the nsis container in Horizon after processing. The script creates the graphs and snap-results folders for the XML graph and output.

No. 3 Ubuntu tools and Python environment

Run the terminal commands in Bash. Copy each command block into the terminal and press Enter.

On Ubuntu, install Python 3 and venv support. The tools curl and jq are used by the authentication instructions. Installing system packages requires sudo privileges:

sudo apt update
sudo apt install python3 python3-venv curl jq

Create a working directory, then create and activate a virtual environment:

mkdir -p "$HOME/nsis-snap-tutorial"
cd "$HOME/nsis-snap-tutorial"
python3 -m venv .venv
source .venv/bin/activate

Install the Python packages in that environment:

python3 -m pip install requests boto3 rasterio numpy matplotlib

No. 4 SNAP graph and Python scripts

Save snap_request.py, nsis_l1c_subset.xml and preview_snap.py in ~/nsis-snap-tutorial.

You can download these files or copy the listings into files with the stated names. Each Python listing starts with a filename comment. Save the files before running the commands. Run all commands from the working directory with the virtual environment active; python3 then uses the interpreter from that environment.

The SNAP overview describes the application, and the Subset operator documentation explains the crop settings used by this graph.

Step 1: Check the input product

The NSIS catalogue lists satellite products and their availability. The example uses this Sentinel-2 L1C product from Poland, acquired on 30 December 2025:

S2A_MSIL1C_20251230T101451_N0511_R022_T33UVV_20251230T103257.SAFE
00975d1b-d7ed-4238-a6ba-5d512fcdf19a

The name ending in .SAFE identifies a package of satellite image files and supporting information. The second line is the catalogue identifier. NSIS reads this product directly during processing.

Save snap_request.py in ~/nsis-snap-tutorial. Click the snap_request.py row to read or copy the code.

snap_request.py
# snap_request.py
"""Submit one catalogue L1C product and a complete graph to NSIS custom_snap.

With no action option, only check public catalogue metadata. Set the documented
NSIS_S3_* variables and ACCESS_TOKEN before using --submit or --resume.
Use --check-access to check the ordering API with ACCESS_TOKEN without creating
an order or accessing S3 storage.
"""
import argparse
import copy
import json
import os
from pathlib import Path
import time
from uuid import uuid4
from urllib.parse import urlparse

import requests

CATALOGUE = 'https://catalogue.nsiscloud.polsa.gov.pl/odata/v1'
ORDERING = 'https://odp.nsiscloud.polsa.gov.pl/odata/v1'
PRODUCT = 'S2A_MSIL1C_20251230T101451_N0511_R022_T33UVV_20251230T103257.SAFE'
GRAPH = Path(__file__).with_name('nsis_l1c_subset.xml')
OUTPUT_NAME = 'nsis_l1c_subset.tif'


def check_product(product_name):
    if '_MSIL1C_' not in product_name or not product_name.endswith('.SAFE'):
        raise ValueError('This graph example requires a Sentinel-2 L1C SAFE product.')
    response = requests.get(CATALOGUE + '/Products', params={
        '$filter': "Name eq '" + product_name.replace("'", "''") + "'",
        '$expand': 'Locations',
    }, timeout=60)
    response.raise_for_status()
    products = response.json()['value']
    if len(products) != 1 or products[0].get('Online') is not True:
        raise RuntimeError('The exact L1C product is not uniquely available online. Select and inspect a compatible L1C input first.')
    return products[0]


def build_order(product_name, endpoint, access_key, secret_key, bucket, run_id):
    products_in_graph = {'infile': {'source_type': 'catalogue', 'source': product_name}}
    output_filenames = {'outfile': OUTPUT_NAME}
    graph_key = f'graphs/{run_id}/{GRAPH.name}'
    output_prefix = f'snap-results/{run_id}'
    workflow_options = [
        {'Name': 's3_endpoint_url', 'Value': endpoint},
        {'Name': 's3_access_key', 'Value': access_key},
        {'Name': 's3_secret_key', 'Value': secret_key},
        {'Name': 'graph_path', 'Value': f's3://{bucket}/{graph_key}'},
        {'Name': 'products_in_graph', 'Value': products_in_graph},
        {'Name': 'output_filenames', 'Value': output_filenames},
        {'Name': 'output_storage', 'Value': 'PRIVATE'},
        {'Name': 'output_dir', 'Value': f's3://{bucket}/{output_prefix}'},
    ]
    order_body = {
        'WorkflowName': 'custom_snap',
        'Name': f'NSIS L1C subset {run_id}',
        'InputProductReference': {'Reference': ''},
        'WorkflowOptions': workflow_options,
    }
    return order_body, graph_key, f'{output_prefix}/{OUTPUT_NAME}'


def redacted_order(order_body):
    visible = copy.deepcopy(order_body)
    for option in visible['WorkflowOptions']:
        if option['Name'] in ('s3_access_key', 's3_secret_key'):
            option['Value'] = '[REDACTED]'
    return visible


def api_request(method, path, **kwargs):
    # Read ACCESS_TOKEN each time so a newly obtained token can be used on restart.
    token = os.environ.get('ACCESS_TOKEN')
    if not token:
        raise RuntimeError('Set ACCESS_TOKEN using the NSIS authentication instructions.')
    response = requests.request(method, ORDERING + path,
                                headers={'Authorization': f'Bearer {token}'},
                                timeout=90, **kwargs)
    if response.status_code in (401, 403):
        if method == 'GET' and path == '/Workflows':
            next_step = ('No order was submitted by this invocation. Sign in again in this '
                         'terminal, then run python3 snap_request.py --check-access.')
        elif method == 'GET' and path.startswith('/BatchOrder('):
            next_step = ('Sign in again in this terminal, then use --resume with the saved '
                         'run JSON to retrieve this order. Do not use --submit.')
        else:
            next_step = ('Check your orders in the NSIS portal before submitting again. '
                         'Use --resume if an order was created and its run JSON was saved.')
        raise RuntimeError(
            f'NSIS ordering API returned HTTP {response.status_code} for {method} {path}. '
            'The access token may have expired or access may have been denied. ' + next_step)
    if response.status_code == 504:
        if method == 'GET' and path == '/Workflows':
            next_step = ('No order was submitted by this invocation. Wait a minute, then '
                         'run python3 snap_request.py --check-access again.')
        elif method == 'GET' and path.startswith('/BatchOrder('):
            next_step = ('The order already exists. Wait a minute, then use --resume '
                         'with the saved run JSON. Do not use --submit.')
        else:
            next_step = ('The submission outcome is unknown. Check your orders in the '
                         'NSIS portal before submitting again.')
        raise RuntimeError(f'NSIS gateway timed out (HTTP 504) for {method} {path}. ' + next_step)
    response.raise_for_status()
    return response.json()


def statuses(payload):
    value = payload.get('value', payload)
    records = value if isinstance(value, list) else [value]
    if not records or any(not isinstance(x, dict) or 'Status' not in x for x in records):
        raise RuntimeError('Unexpected order status response; inspect it in the NSIS portal before continuing.')
    return [str(x['Status']).lower() for x in records]


def check_workflow():
    workflows = api_request('GET', '/Workflows', params={
        '$expand': 'WorkflowOptions', '$filter': "Name eq 'custom_snap'",
    })
    if len(workflows.get('value', [])) != 1:
        raise RuntimeError('The API did not return exactly one custom_snap workflow. '
                           'No order was submitted by this invocation.')


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    action = parser.add_mutually_exclusive_group()
    action.add_argument('--submit', action='store_true', help='Upload the graph and submit a processing order')
    action.add_argument('--check-access', action='store_true', help='Check authenticated workflow access without creating an order or accessing S3')
    action.add_argument('--resume', type=Path, help='Resume status/output checks from a run JSON; never submit another order')
    parser.add_argument('--product', default=PRODUCT)
    args = parser.parse_args()
    if args.check_access:
        print('Checking ordering API access: GET /Workflows (request timeout: 90 seconds)...', flush=True)
        check_workflow()
        print('Ordering API access check passed: custom_snap is listed. No order was created. '
              'S3 access and order submission have not been tested.')
        return
    if args.submit and not GRAPH.is_file():
        raise FileNotFoundError(f'Missing SNAP graph: {GRAPH}. Save the XML as '
                                'nsis_l1c_subset.xml alongside snap_request.py. '
                                'No order was submitted by this invocation.')
    if not args.resume:
        product = check_product(args.product)
        print(json.dumps({key: product[key] for key in ('Id', 'Name', 'Online')}, indent=2))
        if not args.submit:
            print('Public metadata check only. Use --submit after preparing credentials and reviewing the graph.')
            return

    import boto3
    endpoint = os.environ['NSIS_S3_ENDPOINT']
    if urlparse(endpoint).scheme != 'https':
        raise ValueError('NSIS_S3_ENDPOINT must be your HTTPS private-storage endpoint.')
    bucket = os.environ['NSIS_S3_BUCKET']
    access_key = os.environ['NSIS_S3_ACCESS_KEY']
    secret_key = os.environ['NSIS_S3_SECRET_KEY']
    s3 = boto3.client('s3', endpoint_url=endpoint,
                      aws_access_key_id=access_key, aws_secret_access_key=secret_key)
    if args.resume:
        run = json.loads(args.resume.read_text())
        if run['endpoint'] != endpoint or run['bucket'] != bucket:
            raise ValueError('Resume with the same private-storage endpoint and bucket.')
    else:
        check_workflow()
        run_id = uuid4().hex
        order_body, graph_key, output_key = build_order(args.product, endpoint, access_key, secret_key, bucket, run_id)
        print(json.dumps(redacted_order(order_body), indent=2))
        s3.upload_file(str(GRAPH), bucket, graph_key)
        # Submit once: a network timeout after this POST needs an order-list check.
        created = api_request('POST', '/ProductionOrder/OData.CSC.Order', json=order_body)
        order_id = created['value']['Id']
        run = {'order_id': order_id, 'endpoint': endpoint, 'bucket': bucket,
               'output_key': output_key, 'product': args.product}
        run_file = Path(f'snap-run-{run_id}.json')
        run_file.write_text(json.dumps(run, indent=2) + '\n')
        print(f'Order created: {order_id}. Resume file: {run_file}')

    # Stop after one hour; the saved order can be checked again with --resume.
    for _ in range(120):
        payload = api_request('GET', f"/BatchOrder({run['order_id']})")
        states = statuses(payload)
        print('Order status:', ', '.join(states))
        if all(state in ('completed', 'done') for state in states):
            break
        if any(state in ('failed', 'error', 'cancelled', 'canceled') for state in states):
            raise RuntimeError('Processing failed. Inspect StatusMessage in the order response or portal.')
        time.sleep(30)
    else:
        raise TimeoutError('Order has not finished. Use --resume later; do not submit it again.')
    output = Path(f"{run['order_id']}-{OUTPUT_NAME}")
    if output.exists():
        raise FileExistsError(output)
    s3.download_file(bucket, run['output_key'], str(output))
    import rasterio
    import numpy as np
    with rasterio.open(output) as dataset:
        if dataset.count != 2 or (dataset.height, dataset.width) != (512, 512) or dataset.crs is None:
            raise RuntimeError('Unexpected GeoTIFF shape, bands or CRS; review the graph and service output.')
        pixels = dataset.read(masked=True).astype(float)
        if not np.any(np.isfinite(pixels.filled(np.nan)) & (pixels.filled(0) != 0)):
            raise RuntimeError('The selected subset has no usable pixels. Inspect the scene and change the graph region.')
    print('Readable two-band GeoTIFF:', output)


if __name__ == '__main__':
    main()

First check that the input product is available. This command checks the catalogue and then returns to the terminal prompt; it does not start SNAP processing or download a result. Run it in your Bash terminal from ~/nsis-snap-tutorial, with the virtual environment active:

python3 snap_request.py

If the output contains “Online”: true, the catalogue check has succeeded. The final message Public metadata check only. Use –submit after preparing credentials and reviewing the graph. is expected and is not an error.

Successful public catalogue check for the Sentinel-2 input product

The catalogue returns Online: true for the input product. The script then stops; no processing order has been created.

Continue to Step 2 to prepare the graph and enter your storage settings. In Step 3, you will run python3 snap_request.py –submit to start processing.

Step 2: Prepare the graph and storage settings

The supplied nsis_l1c_subset.xml graph reads the image, cuts out a 512 by 512 pixel area and saves it as nsis_l1c_subset.tif. It includes two image layers, called bands B3 and B8. Use the supplied file unchanged for this example:

Save nsis_l1c_subset.xml in ~/nsis-snap-tutorial, alongside snap_request.py. The filename must be exactly nsis_l1c_subset.xml. If your browser adds a suffix such as (1), rename the downloaded file before submitting an order. Click the nsis_l1c_subset.xml row to read or copy the XML.

nsis_l1c_subset.xml
<graph id="NSIS-L1C-subset">
  <version>1.0</version>
  <node id="Read">
    <operator>Read</operator>
    <sources/>
    <parameters class="com.bc.ceres.binding.dom.XppDomElement">
      <file>${infile}</file>
    </parameters>
  </node>
  <node id="Subset">
    <operator>Subset</operator>
    <sources><sourceProduct refid="Read"/></sources>
    <parameters class="com.bc.ceres.binding.dom.XppDomElement">
      <sourceBands>B3,B8</sourceBands>
      <region>5000,5000,512,512</region>
      <referenceBand>B3</referenceBand>
      <copyMetadata>true</copyMetadata>
    </parameters>
  </node>
  <node id="Write">
    <operator>Write</operator>
    <sources><sourceProduct refid="Subset"/></sources>
    <parameters class="com.bc.ceres.binding.dom.XppDomElement">
      <file>${outfile}</file>
      <formatName>GeoTIFF</formatName>
    </parameters>
  </node>
</graph>

Keep the parameter names infile and outfile. The Python script supplies the input and output paths through those parameters. It uploads the XML file when you submit the order in Step 3. NSIS then executes the instructions in the graph.

In the same Bash terminal where ACCESS_TOKEN is set, run this block to enter the storage settings. At each prompt, enter the value from Prerequisite No. 2 and press Enter. The key values are hidden while you type. Keep this terminal open for Step 3:

read -r -p 'Private S3 HTTPS endpoint: ' NSIS_S3_ENDPOINT
read -r -p 'Private S3 bucket: ' NSIS_S3_BUCKET
read -r -s -p 'Private S3 access key: ' NSIS_S3_ACCESS_KEY; printf '\n'
read -r -s -p 'Private S3 secret key: ' NSIS_S3_SECRET_KEY; printf '\n'
export NSIS_S3_ENDPOINT NSIS_S3_BUCKET NSIS_S3_ACCESS_KEY NSIS_S3_SECRET_KEY
Entering the private S3 endpoint, bucket and hidden access keys in Bash

Enter the endpoint and bucket prepared in Prerequisite No. 2. The access key and secret key are hidden while you type.

Step 3: Submit the order and download the result

The script uploads the graph and creates a processing order. In the API request, WorkflowName is set to custom_snap, the workflow used to run a supplied SNAP graph. The script waits for completion, then downloads and checks the result. Submitting an order starts work on NSIS Cloud and may consume your service allocation.

In the same Bash terminal, run this command once from ~/nsis-snap-tutorial, with the virtual environment active:

python3 snap_request.py --submit

The terminal prints the product information, the request with hidden S3 keys, and the order status. This example shows the output after submission. Your order number and run filename will differ; the S3 values come from your storage settings:

{
  "Id": "00975d1b-d7ed-4238-a6ba-5d512fcdf19a",
  "Name": "S2A_MSIL1C_20251230T101451_N0511_R022_T33UVV_20251230T103257.SAFE",
  "Online": true
}
{
  "WorkflowName": "custom_snap",
  "Name": "NSIS L1C subset a89ae2e04eaf41aebe8fa27330c194ac",
  "InputProductReference": {
    "Reference": ""
  },
  "WorkflowOptions": [
    {
      "Name": "s3_endpoint_url",
      "Value": "https://s3.waw4-1.cloudferro.com"
    },
    {
      "Name": "s3_access_key",
      "Value": "[REDACTED]"
    },
    {
      "Name": "s3_secret_key",
      "Value": "[REDACTED]"
    },
    {
      "Name": "graph_path",
      "Value": "s3://nsis/graphs/a89ae2e04eaf41aebe8fa27330c194ac/nsis_l1c_subset.xml"
    },
    {
      "Name": "products_in_graph",
      "Value": {
        "infile": {
          "source_type": "catalogue",
          "source": "S2A_MSIL1C_20251230T101451_N0511_R022_T33UVV_20251230T103257.SAFE"
        }
      }
    },
    {
      "Name": "output_filenames",
      "Value": {
        "outfile": "nsis_l1c_subset.tif"
      }
    },
    {
      "Name": "output_storage",
      "Value": "PRIVATE"
    },
    {
      "Name": "output_dir",
      "Value": "s3://nsis/snap-results/a89ae2e04eaf41aebe8fa27330c194ac"
    }
  ]
}
Order created: 20185216. Resume file: snap-run-a89ae2e04eaf41aebe8fa27330c194ac.json
Order status: queued
Order status: queued
Order status: queued
Order status: queued

The queued status means that the order has been accepted and is waiting for processing. Leave the script running while it checks progress.

The terminal prints Order created: with the order number and a filename such as snap-run-<run-id>.json. Keep that JSON file: it records the order number and output location so you can resume the same order. It does not store tokens or keys.

Uploaded nsis_l1c_subset.xml graph in the nsis container in Horizon

The uploaded nsis_l1c_subset.xml appears under graphs/<run-id> in the nsis container. The folder name identifies the run.

The script checks progress every 30 seconds for up to one hour. After processing finishes, it downloads the TIFF to the working directory. The filename contains the order number followed by -nsis_l1c_subset.tif. The message Readable two-band GeoTIFF: confirms that the file passed the checks and gives its local path.

Order 20185216 completed and its downloaded two-band GeoTIFF passed the checks

Order 20185216 has completed, and the script has downloaded 20185216-nsis_l1c_subset.tif. The final message confirms that the TIFF passed the two-band readability check.

If the script stops before retrieving the result, follow Resume an existing order in the troubleshooting section. Do not submit another order just to retrieve an existing result.

Step 4: Create a PNG preview

The TIFF may appear very dark in an ordinary image viewer. The preview script adjusts the display brightness and creates a PNG containing separate views of bands B3 and B8. It leaves the original TIFF unchanged.

Save preview_snap.py in ~/nsis-snap-tutorial. Click the preview_snap.py row to read or copy the code.

preview_snap.py
# preview_snap.py
"""Create a PNG preview of the B3/B8 output from the supplied SNAP graph."""
import argparse
from pathlib import Path

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import rasterio


def stretch(band):
    """Exclude nodata/non-finite values and stretch the valid 2–98% range."""
    band = np.ma.masked_invalid(np.ma.asarray(band, dtype=float))
    values = band.compressed()
    if not values.size:
        raise ValueError('A band contains no valid pixels.')
    low, high = np.percentile(values, [2, 98])
    if high <= low:
        raise ValueError('A band has no contrast between its 2nd and 98th percentiles.')
    return np.ma.clip((band - low) / (high - low), 0, 1), low, high


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('tiff', type=Path, help='Downloaded two-band SNAP GeoTIFF')
    parser.add_argument('--output', type=Path, default=Path('snap-subset-preview.png'))
    args = parser.parse_args()
    if args.output.suffix.lower() != '.png':
        parser.error('--output must name a PNG file.')
    if args.output.exists():
        parser.error('Output already exists. Choose a different --output filename.')

    with rasterio.open(args.tiff) as dataset:
        if dataset.count != 2:
            parser.error('Expected the two-band B3/B8 output from the supplied graph.')
        bands = dataset.read(masked=True)
    try:
        previews = [stretch(band) for band in bands]
    except ValueError as error:
        parser.error(str(error))

    # These band names and their order match nsis_l1c_subset.xml.
    names = ('B3', 'B8')
    cmap = plt.get_cmap('gray').copy()
    cmap.set_bad('#808080')
    figure, axes = plt.subplots(1, 2, figsize=(11, 6))
    figure.subplots_adjust(left=.06, right=.98, bottom=.15, top=.82, wspace=.18)
    for name, axis, (preview, low, high) in zip(names, axes, previews):
        axis.imshow(preview, cmap=cmap, vmin=0, vmax=1, interpolation='nearest')
        axis.set_title(f'{name}: 2–98% display stretch\nDN {low:.0f}–{high:.0f}')
        axis.set_xlabel('Column')
        axis.set_ylabel('Row')
        print(f'{name}: display range {low:.2f}–{high:.2f} DN')
    figure.suptitle('NSIS SNAP subset — separate band previews', fontsize=15, y=.97)
    figure.text(.5, .055, 'Display adjustment only; bands stretched independently.',
                ha='center', fontsize=10)
    figure.text(.5, .02, 'Nodata shown in grey. Original TIFF unchanged.',
                ha='center', fontsize=10)
    try:
        figure.savefig(args.output, dpi=140)
    finally:
        plt.close(figure)
    print('Preview saved:', args.output.resolve())


if __name__ == '__main__':
    main()

Run this command block in your Bash terminal from ~/nsis-snap-tutorial, with the virtual environment active. When prompted, enter the local path previously printed after Readable two-band GeoTIFF::

read -r -p 'Downloaded GeoTIFF path: ' TIFF_FILE
python3 preview_snap.py "$TIFF_FILE" --output snap-subset-preview.png
Running preview_snap.py on the downloaded TIFF and saving the PNG preview

Enter the TIFF filename printed at the end of Step 3. This run saves snap-subset-preview.png in the working directory.

Open snap-subset-preview.png in an image viewer. It contains two greyscale panels labelled B3 and B8. The script prints the full path to the PNG. If that filename already exists, choose another name with –output.

PNG preview with band B3 on the left and band B8 on the right

The preview places band B3 on the left and B8 on the right. Display brightness is adjusted separately for each panel; the original TIFF is unchanged.

Troubleshooting

HTTP 504 while checking /Workflows: the NSIS gateway did not receive a response from the ordering service in time. This request only reads the workflow list, so it does not create an order. Wait a minute, then retry from ~/nsis-snap-tutorial in the same Bash terminal, with the virtual environment active:

python3 snap_request.py --check-access

The script immediately prints Checking ordering API access: while it waits for a response. If the timeout persists, report HTTP 504 and GET /Workflows to NSIS support. An order that has already completed does not need to be submitted again.

HTTP 401 or 403 from the ordering API: the error identifies the HTTP status and the request that failed. Sign in again using the instructions in Prerequisite No. 1, in the same Bash terminal. An expired token can cause this error; the message alone does not establish that your account lacks processing permission.

After signing in, run this command from ~/nsis-snap-tutorial, with the virtual environment active:

python3 snap_request.py --check-access

This command checks whether the ordering API returns the custom_snap workflow. It does not create an order, upload a graph or test S3 access. A successful check prints Ordering API access check passed:. If the API still rejects a fresh token, report the HTTP status and request path to NSIS support.

Use the failed request to decide how to continue:

  • GET /Workflows: the script stopped before submitting an order. After the access check succeeds, you can run python3 snap_request.py –submit.

  • GET /BatchOrder(…): an order already exists. Use –resume with its saved JSON file as described in Resume an existing order.

  • POST /ProductionOrder/OData.CSC.Order: check your orders in the NSIS portal before trying another submission.

Resume an existing order: if the token expires or the waiting period ends, obtain a fresh token in the same terminal. Keep the storage variables from Step 2 set. Run this command block in your Bash terminal from ~/nsis-snap-tutorial, with the virtual environment active. Enter the saved JSON filename when prompted:

read -r -p 'Saved run JSON filename: ' RUN_FILE
python3 snap_request.py --resume "$RUN_FILE"

The command checks the existing order and retrieves its output without creating a new order. Pressing Ctrl+C stops the local script but does not cancel processing in NSIS. Use –resume when you return.

Submission times out before an order number is saved: check your orders in the NSIS portal before trying again. The service may already have accepted the request.

Processing fails or the result is missing: check StatusMessage and the processing logs in NSIS. Keep the order number when contacting support. Submitting the same order again does not explain the failure.

What to do next