Skip to content

Python SDK Reference

Python client for the Neuramancer fake-detection API. Every API endpoint has its own file containing a pure function you can import, or run directly as a CLI script.

Follow the Onboarding Guide to install dependencies and set up your environment before making API calls.

Run every E2E test included in your SDK package:

Terminal window
bash run_all_e2e.sh
OptionDescription
SKIP_ANALYSIS=1 bash run_all_e2e.shSkip the analysis_run_e2e test (fast check)

The script will:

  1. Activate the Python venv if present
  2. Run each *_e2e.py test sequentially
  3. Print a pass / fail / skip summary at the end
  4. Exit with code 1 if any test failed

Alternatively, you can run each individual *_e2e.py test in isolation.

Every file accepts -h / --help. Output is JSON and can be piped to jq.

Make sure the venv is activated first: source venv/bin/activate

Terminal window
# Create an analysis
python api_requests/create_analysis.py ./trial_images/fake.jpg --width 720 --height 960 | jq
# Create without upload (record only — upload separately, then start)
python api_requests/create_analysis.py ./trial_images/fake.jpg --width 720 --height 960 --no-upload | jq
# <analysis_id> stands for a 24-character hex ObjectId returned by the create_analysis call like this: 65a1b2c3d4e5f6a7b8c9d0e1
# Start analysis inference (after uploading separately)
python api_requests/start_analysis.py <analysis_id> | jq
# Get a single analysis
python api_requests/get_analysis.py <analysis_id> | jq
# Poll until complete
python api_requests/get_analysis.py <analysis_id> --poll --interval 5 --max-attempts 60 | jq
# List analyses
python api_requests/list_analyses.py --search <search_term> --limit 10 | jq
# Delete
python api_requests/delete_analysis.py <analysis_id> | jq
# Retry with different options
python api_requests/retry_analysis.py <analysis_id> --tier forensicReporting | jq
# Tenant info
python api_requests/get_tenant.py | jq
python api_requests/update_tenant.py --s3-from-env | jq
python api_requests/get_tenant_report.py range --start 2025-01-01 --end 2027-01-31 | jq
# API key management
python api_requests/get_apikeys.py | jq
python api_requests/create_apikey.py "Production Key" 365 | jq
python api_requests/delete_apikey.py <uuid> | jq
# Health / ping
python api_requests/health.py | jq
python api_requests/ping.py | jq

Import from the api_requests package to load all functions at once, or import individual modules:

# Load all request functions
from api_requests import create_analysis, poll_analysis
# Or import from individual modules
# from api_requests.create_analysis import create_analysis
# from api_requests.get_analysis import poll_analysis
# Create
result = create_analysis(True, "./image.jpg", {
"width": 720,
"height": 960,
"tier": "forensicReporting",
})
analysis_id = result["data"][0]["id"]
print(f"Created in {result['_meta']['durationMs']}ms")
# Poll until done
final = poll_analysis(analysis_id, interval_sec=5, max_attempts=60)
analysis = final["data"][0]
print(analysis["inferenceStatus"]["result"]["decision"])

See ./demo_library_usage.py for a runnable example.

All SDK functions return the API response body 1:1 as a dict, with one additional key _meta containing SDK-produced runtime metadata:

{
# ---- API response body (1:1, varies by endpoint) ----
"success": True,
"data": [ ... ],
# ---- SDK metadata (always present) ----
"_meta": {
"durationMs": 123.45, # Wall-clock time in milliseconds
"status": 200, # HTTP status code
"error": None, # Always None on success
}
}

All functions throw RuntimeError on API errors (non-2xx status codes or connection failures). Wrap calls in try/except to handle errors:

try:
result = get_analysis("abc123")
except RuntimeError as e:
print(f"API error: {e}")

create_analysis() adds extra fields to _meta when upload=True (the default):

_meta keyTypeDescription
durationMsfloatTotal time (create + upload + finish)
statusintHTTP status of the create call
errorNoneAlways None on success
uploadResultdictUpload response (success, status, duration_ms, error)
startAnalysisResultdictStart-analysis API response body

Creates an analysis, uploads an image via a presigned URL, and finalises the upload (3-step flow).

When --no-upload is passed (or upload=False in code), only step A is executed — the analysis record is created but no file is uploaded.

Terminal window
python create_analysis.py <image> --width <int> --height <int> \
[--format jpg|png] [--name NAME] [--comment TEXT] \
[--model MODEL] [--decision-strategy STRATEGY] \
[--tier flagging|forensicReporting] \
[--seed INT] [--no-upload]

Function signature:

create_analysis(upload: bool = True, image_path: str = '', options: dict | None = None) -> dict
ParameterRequiredDefaultDescription
upload—TrueWhether to upload and then start the analysis
image_pathwhen upload=True''Path to the image file
--width✔—Image width in pixels (>64, ≤8192)
--height✔—Image height in pixels (>64, ≤8192)
--name—filenameDisplay name for the analysis
--format—auto-detectjpg or png
--comment—""Free-text comment
--model—nais-image-latestInference model identifier
--decision-strategy—defaultdefault|pedantic|relaxed
--tier—flaggingflagging or forensicReporting
--seed—1337Random seed for inference reproducibility
--no-upload——Only create the record (skip upload + finalise)

--tier defaults to flagging. Set --tier forensicReporting for the full forensic result set. See Cost Model.

The library function create_analysis() also accepts filename (original filename) and tags (list of strings) via the options dict.

For S3-backed analyses, the API response includes storageUrls.objectKey for the uploaded input file and generated result files. This is the exact object path in the bucket.

Example:

bucket-hash/analysis-image-697fbb59f0630-857f5013877f5dbb5cb

If you split this value at /, the last segment is the filename in object storage:

analysis-image-697fbb59f0630-857f5013877f5dbb5cb

That filename suffix is a SHA-256 hash of a random UUID. It is generated intentionally so filenames do not collide in storage. For direct bucket access, always use the full storageUrls.objectKey, not only the final filename segment.

Retrieves a single analysis by ID (24-character hex ObjectId).

Terminal window
python get_analysis.py <analysis_id> [--poll] [--interval SECONDS] [--max-attempts N]

Pass --poll to automatically poll until the analysis reaches completed or failed (default: 10 s interval, 30 max attempts). The first poll waits INITIAL_POLL_TIME_SECS (21 s) to account for p99 processing time.

Lists analyses with optional search and pagination.

Terminal window
python list_analyses.py [--search TEXT] [--start INT] [--limit INT] [--tenant NAME]
ParameterDefaultDescription
--search—Free-text search over name and comment
--start0Zero-based pagination offset
--limit25Page size (≥ 1)
--tenant.envTenant name override

Soft-deletes an analysis by ID.

Terminal window
python delete_analysis.py <analysis_id>

Re-runs inference on an existing analysis, optionally with updated options.

Terminal window
python retry_analysis.py <analysis_id> \
[--tier flagging|forensicReporting] [--model MODEL] \
[--decision-strategy STR]

Valid --decision-strategy values: default, pedantic, relaxed.

Confirms an analysis upload and triggers inference (after create_analysis() was called with --no-upload).

Terminal window
python start_analysis.py <analysis_id> [--tenant-name NAME]

Retrieves tenant details (storage config, masked API keys).

Terminal window
python get_tenant.py [TENANT_NAME]

Updates tenant settings. Pass --s3-from-env to populate S3 config from .env variables.

Terminal window
python api_requests/update_tenant.py [--s3-from-env] [KEY=VALUE ...]

🚨 DANGER: This overwrites existing tenant settings. Ensure your .env file reflects the intended state.

Get Tenant Report — get_tenant_report.py

Section titled “Get Tenant Report — get_tenant_report.py”

Retrieves a tenant usage report (total or date-range).

Terminal window
python get_tenant_report.py [total|range] [--start DATE] [--end DATE]
Terminal window
python get_apikeys.py [TENANT_NAME]
Terminal window
python create_apikey.py <key_name> <expiry_in_days>

⚠️ WARNING: The clear-text key is shown only once. Store it securely.

Terminal window
python delete_apikey.py <uuid>
ScriptDescription
ping.pyLightweight connectivity check (no auth required)
health.pyHealth check (no auth required) returning server uptime

The SDK ships integration tests that exercise full API workflows. They double as runnable usage examples.

ScriptAuthWorkflow
analysis_run_e2e.pyApiKeyRuns inference on fake.jpg (expects “fake”) and real.jpg (expects “real”), verifying classification for each.
apikey_crud_e2e.pyApiKeyCreate a temporary API key ⇒ list & verify ⇒ delete ⇒ verify removal.
list_analyses_e2e.pyApiKeyList analyses ⇒ search by name ⇒ paginate.
monitoring_e2e.pyApiKeyPing (no auth) ⇒ authenticated health check.
tenant_report_e2e.pyApiKeyGet tenant info ⇒ fetch total usage report.
update_tenant_e2e.pyApiKeyRead tenant ⇒ set test webhook URL ⇒ verify ⇒ restore original ⇒ verify.
analysis_lifecycle_e2e.pyApiKeyFull lifecycle: create (no upload) ⇒ manual upload ⇒ start ⇒ poll ⇒ get ⇒ list (search, pagination) ⇒ retry ⇒ poll ⇒ verify diff ⇒ delete ⇒ confirm 404.
Terminal window
# Run a single test
python analysis_run_e2e.py
# Run all tests
bash run_all_e2e.sh
TierDescription
flagging (default)Fast binary flag — returns only decision + confidence. Fields like predictions, heatmaps, pdfReportStorageUrls, text-content fields are null.
forensicReportingFull forensic analysis — all fields populated, PDF export available.

Set the tier when creating an analysis:

Terminal window
python api_requests/create_analysis.py image.jpg --width 720 --height 960 --tier forensicReporting
Terminal window
# Run 4 parallel create-and-poll cycles
bash run_benchmark.sh 4

Requires jq. See run_benchmark.sh for tuneable parameters.