Skip to content
English
  • There are no suggestions because the search field is empty.

08.07 Building a Script Against the API

End-to-end example scripts (bash and Python) showing how to authenticate, list risks, create a risk, update a risk, and handle errors. Use these as starting points for real integrations; productionize by adding secrets management, retries, logging, and tests.

Why this matters

Most "how do I integrate with SimpleRisk" conversations end up at the same place: a script that authenticates, hits a few endpoints, processes the responses. The Swagger UI tells you what's possible; this article shows you what the working code looks like for the most common patterns. Use the examples as starting points; productionize from there.

Prerequisites

Before running these examples:

  • A SimpleRisk instance you can hit — preferably non-production for testing.
  • An API Extra-active install with the api master toggle on. See API Overview.
  • An API key for an integration user with appropriate permissions. See API Key Management.
  • The base URL of your instance.
  • For bash examples: curl and jq installed.
  • For Python examples: Python 3.8+ and the requests library (pip install requests).

In all examples below, replace placeholders:

  • → e.g., simplerisk.example.com.
  • → the 64-character API key you generated.

Example 1: List risks (bash)

The simplest integration: authenticate and read the risk list.

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://
  
   " API_KEY="${SIMPLERISK_API_KEY:?Set SIMPLERISK_API_KEY environment variable}" response=$(curl -sS \ -H "X-API-KEY: ${API_KEY}" \ -H "Accept: application/json" \ "${BASE_URL}/api/v2/risks") # Check the response status code status=$(echo "${response}" | jq -r '.status_code') if [ "${status}" != "200" ]; then echo "Error: status ${status}, message: $(echo "${response}" | jq -r '.status_message')" >&2 exit 1 fi # Print risk count and a summary count=$(echo "${response}" | jq '.data | length') echo "Found ${count} risks" echo "${response}" | jq -r '.data[] | "\(.id): \(.subject) (status: \(.status))"' 
  

Save as list_risks.sh, make executable (chmod +x list_risks.sh), set SIMPLERISK_API_KEY in your environment, run.

The pattern: curl with the header, jq to parse JSON, error-check on status_code. Adapt to your needs.

Example 2: List risks (Python)

The same operation in Python:

#!/usr/bin/env python3
"""List risks from SimpleRisk."""
import os
import sys
import requests

BASE_URL = "https://
  
   " API_KEY = os.environ.get("SIMPLERISK_API_KEY") if not API_KEY: print("Set SIMPLERISK_API_KEY environment variable", file=sys.stderr) sys.exit(1) response = requests.get( f"{BASE_URL}/api/v2/risks", headers={ "X-API-KEY": API_KEY, "Accept": "application/json", }, timeout=30, ) if response.status_code != 200: print(f"Error: status {response.status_code}, body: {response.text}", file=sys.stderr) sys.exit(1) data = response.json() risks = data.get("data", []) print(f"Found {len(risks)} risks") for risk in risks: print(f" {risk['id']}: {risk['subject']} (status: {risk.get('status')})") 
  

Save as list_risks.py, set SIMPLERISK_API_KEY, run with python3 list_risks.py.

Example 3: Create a risk (Python)

A more involved example: submit a new risk via POST /api/v2/risks/submit.

#!/usr/bin/env python3
"""Submit a new risk to SimpleRisk."""
import os
import sys
import requests

BASE_URL = "https://
  
   " API_KEY = os.environ.get("SIMPLERISK_API_KEY") if not API_KEY: print("Set SIMPLERISK_API_KEY environment variable", file=sys.stderr) sys.exit(1) # The new risk's data new_risk = { "subject": "Vendor X has not completed annual security questionnaire", "reference_id": "VRM-2026-042", "regulation": 1, # ID of the relevant regulation lookup "control_number": "CC9.2", # the control reference "location": [3], # location IDs (an array because location is multi-select) "source": 2, # source ID "category": 5, # category ID "team": [4, 7], # team IDs the risk should be visible to "technology": [1, 2], # technology IDs "owner": 12, # owner user ID "manager": 8, # manager user ID "assessment": "The vendor's annual security questionnaire was due 2026-03-31 and has not been received. Without it we lack confirmation of their security posture.", "notes": "Initial follow-up sent 2026-04-15; second follow-up 2026-04-30.", # Scoring (using Classic methodology — adjust per your install) "scoring_method": 1, "CLASSIC_likelihood": 3, "CLASSIC_impact": 4, } response = requests.post( f"{BASE_URL}/api/v2/risks/submit", headers={ "X-API-KEY": API_KEY, "Accept": "application/json", "Content-Type": "application/json", }, json=new_risk, timeout=30, ) if response.status_code not in (200, 201): print(f"Error: status {response.status_code}", file=sys.stderr) print(f"Response: {response.text}", file=sys.stderr) sys.exit(1) result = response.json() risk_id = result.get("data", {}).get("id") print(f"Created risk {risk_id}") 
  

The exact field set varies by your install's configuration (custom fields, scoring methodology, dropdown values). Use the Swagger UI's POST /risks/submit schema as the canonical reference for what fields are accepted.

Example 4: Update a risk (Python)

Modify an existing risk via PATCH /api/v2/risks/{id}:

#!/usr/bin/env python3
"""Update a risk's status in SimpleRisk."""
import os
import sys
import requests

BASE_URL = "https://
  
   " API_KEY = os.environ.get("SIMPLERISK_API_KEY") RISK_ID = sys.argv[1] if len(sys.argv) > 1 else None if not API_KEY: print("Set SIMPLERISK_API_KEY environment variable", file=sys.stderr) sys.exit(1) if not RISK_ID: print("Usage: update_risk.py 
   
    ", file=sys.stderr) sys.exit(1) # Fields to update update = { "status": "Closed", "close_reason": 1, "notes": "Closed via integration; vendor questionnaire received 2026-05-12.", } response = requests.patch( f"{BASE_URL}/api/v2/risks/{RISK_ID}", headers={ "X-API-KEY": API_KEY, "Accept": "application/json", "Content-Type": "application/json", }, json=update, timeout=30, ) if response.status_code != 200: print(f"Error: status {response.status_code}", file=sys.stderr) print(f"Response: {response.text}", file=sys.stderr) sys.exit(1) print(f"Updated risk {RISK_ID}") 
   
  

The PATCH endpoint accepts only the fields you want to change; omitted fields are unchanged.

Example 5: Polling for changes

For "near-real-time" integration without webhooks: poll the risk list periodically, dispatch on changes.

#!/usr/bin/env python3
"""Poll SimpleRisk for risk changes and dispatch to a downstream system."""
import json
import os
import sys
import time
from pathlib import Path

import requests

BASE_URL = "https://
  
   " API_KEY = os.environ.get("SIMPLERISK_API_KEY") STATE_FILE = Path("./risk_state.json") POLL_INTERVAL = 300 # 5 minutes def fetch_risks(): response = requests.get( f"{BASE_URL}/api/v2/risks", headers={"X-API-KEY": API_KEY, "Accept": "application/json"}, timeout=30, ) response.raise_for_status() return {r["id"]: r for r in response.json().get("data", [])} def load_state(): if STATE_FILE.exists(): return json.loads(STATE_FILE.read_text()) return {} def save_state(state): STATE_FILE.write_text(json.dumps(state)) def dispatch_change(event_type, risk): """Replace this with your downstream integration (Slack post, etc.)""" print(f"[{event_type}] {risk['id']}: {risk['subject']}") def main(): while True: try: current = fetch_risks() previous = load_state() # New risks for risk_id, risk in current.items(): if str(risk_id) not in previous: dispatch_change("NEW", risk) # Removed risks for risk_id in previous: if risk_id not in {str(k) for k in current}: dispatch_change("REMOVED", {"id": risk_id, "subject": "(removed)"}) # Update state save_state({str(k): v for k, v in current.items()}) except requests.RequestException as e: print(f"Polling failed: {e}", file=sys.stderr) # Don't update state on failure; retry next cycle time.sleep(POLL_INTERVAL) if __name__ == "__main__": main() 
  

This is a starting-point pattern. Productionize:

  • Detect changes (not just new/removed) by comparing fields.
  • Use exponential backoff on persistent failures.
  • Log to a structured logger, not stdout.
  • Run as a long-running service (systemd, Docker, k8s) rather than a script.
  • Add metrics (polls per minute, dispatch count, error rate).

Productionizing scripts

Scripts that work in dev need work to be production-ready:

Secrets management

Don't export SIMPLERISK_API_KEY=... in a developer's shell history. Use:

  • A secrets manager (Vault, AWS Secrets Manager, Azure Key Vault) — fetch the key at runtime.
  • Encrypted environment files (sops, encrypted .env) loaded by the deployment environment.
  • Per-developer keys with short rotation cycles for development; per-environment keys for staging/production.

Error handling and retries

Real integrations encounter:

  • Transient network errors (retry with backoff).
  • 5xx errors from SimpleRisk during deploys (retry with backoff).
  • 401/403 errors (don't retry; alert).
  • 429 errors if a reverse proxy enforces rate limits (retry with Retry-After).
  • Malformed responses (don't crash; log and skip).

Use a sturdy HTTP client library (Python's requests with urllib3.util.retry.Retry, or higher-level libs like tenacity) that handles retry/backoff without you reinventing the wheel.

Logging

Log structured events: timestamp, request URL, response status, response time, integration-specific context. Avoid logging full response bodies (risk descriptions can contain sensitive content). Send logs to a centralized aggregator (ELK, Splunk, Datadog) for searchability and alerting.

Testing

Write tests:

  • Unit tests for the parsing and processing logic (mock the API responses).
  • Integration tests against a non-production SimpleRisk instance (use a dedicated test user / API key).
  • Smoke tests that run after deployment to confirm the integration is working end-to-end.

Monitoring and alerting

Production integrations should emit metrics: requests per minute, error rate, dispatch latency. Alert on:

  • Error rate above a threshold (the integration is failing).
  • Zero requests for an extended period (the integration is hung).
  • Authentication failures (the key was rotated or revoked).

Deployment

Run the integration as a long-running service:

  • Cron for periodic scripts.
  • systemd services for long-running daemons.
  • Docker / Kubernetes for containerized deployments.
  • AWS Lambda / Cloud Functions for event-driven serverless patterns.

The choice depends on the integration's lifecycle and your operational stack.

Common pitfalls

A handful of patterns recur with API scripts.

  • Hardcoding the API key in the script. Use environment variables or a secrets manager.

  • Hardcoding the base URL. Same — use environment variables. Same script should work against dev, staging, and production with only configuration changes.

  • No timeout on requests. A hung SimpleRisk causes the script to hang indefinitely. Always set a timeout.

  • No error handling. Production scripts encounter errors; handle them gracefully.

  • Polling at high frequency. 1-second polling is rarely necessary; 5-minute polling is plenty for most use cases. Be a good citizen.

  • Treating the script as the integration. A bash script in a developer's home directory isn't an integration. Move it to a deployment surface (cron, systemd, Docker) with monitoring and operational ownership.

  • Not version-pinning dependencies. A Python script that uses requests==2.30.0 in dev but the latest in production might behave differently.

  • Not documenting the script. Future-you (or your colleagues) will need to know what it does, why, and how to operate it.

  • Blocking the main thread on long operations. A script that does a long-running operation per risk in a loop can take hours; consider concurrency (with appropriate rate limiting) or async patterns.

  • Logging API keys, full responses with sensitive content, or other secrets. Restrain what you log.

Related

Reference

  • Bash dependencies: curl, jq.
  • Python dependencies: Python 3.8+, requests library.
  • Authentication: X-API-KEY header.
  • Standard URL pattern: https:// /api/v2/ — always use /api/v2/ explicitly.
  • Endpoint reference: The Swagger UI at /api/v2/documentation.php is the canonical source for endpoint shapes, parameters, and response formats.
  • Error handling baseline: Check the response's status_code (or HTTP status); handle 4xx (client errors — fix the request, don't retry) and 5xx (server errors — retry with backoff) differently.