Skip to content
Vulnotes LogoVulnotes
Python SDK

Python SDK

The official Python SDK is a small wrapper around the Vulnotes REST API. It handles authentication, pagination, file uploads, exports, retries, and API errors while keeping responses close to the JSON returned by the API.

The SDK requires Python 3.9 or later.

Installation

bash
pip install vulnotes

Authentication

Create a key under Administration > Settings > API Keys. Only grant the permissions your script needs. See API Keys for the available scopes and how access is restricted.

Pass the instance URL and key when creating the client:

python
from vulnotes import VulnotesClient

client = VulnotesClient(
    "https://acme.vulnotes.app",
    api_key="vuln_sk_...",
)

You can also keep the credentials out of your source code:

bash
export VULNOTES_URL="https://acme.vulnotes.app"
export VULNOTES_API_KEY="vuln_sk_..."
python
from vulnotes import VulnotesClient

client = VulnotesClient()

The client adds /api to the instance URL when needed. Use the base URL of your Vulnotes instance, not the URL of a specific API endpoint.

Basic usage

SDK methods return regular Python dictionaries and lists. This makes it easy to use the result directly or pass it to an existing script.

python
from vulnotes import VulnotesClient

with VulnotesClient() as client:
    report = client.reports.create(
        "External pentest Q3",
        language="EN",
    )

    client.findings.add(report["_id"], {
        "title": "SQL injection in /login",
        "severity": "Critical",
        "cvss": {
            "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
        },
        "data": {
            "EN": {
                "title": "SQL injection in /login",
                "description": "The username parameter is injectable."
            }
        },
    })

Using the client as a context manager closes its HTTP session when the block finishes. You can also call client.close() yourself.

Available resources

Methods are grouped by resource on the client:

ResourceWhat it covers
client.reportsCreate, search, update, import, export, and delete reports
client.findingsList, add, update, reorder, and delete report findings
client.companiesCompanies, contacts, and client portal access
client.templatesReport templates, revisions, content, and previews
client.vulnerability_templatesVulnerability templates and report template links
client.vulnerabilitiesThe vulnerability library
client.snapshotsReport review snapshots and diffs
client.commentsReview comments, annotations, and resolution status
client.notesReport notes and pinning
client.imagesImage uploads and associations
client.attachmentsFile uploads and associations
client.planningCalendar events, availability, users, and conflict checks
client.aiVulnerability generation, translation, content improvement, and screenshot analysis
client.api_keysThe current key and its effective permissions

The API reference documents the request and response fields used by these methods.

Pagination

List methods accept page and limit when the endpoint supports pagination:

python
result = client.reports.list(page=1, limit=25)

For scripts that need every item, use an iterator. Pages are fetched as they are needed:

python
for report in client.reports.iter(limit=100):
    print(report["title"])

Iterators are available for reports, companies, report templates, vulnerability templates, vulnerabilities, and planning events.

Structured template authoring

The structured authoring API edits report templates without replacing opaque builder HTML. Start by reading the server's schema and the current authoring document:

python
schema = client.templates.authoring_schema()
current = client.templates.authoring_document(template_id)
version = current["contentVersion"]

The schema endpoint is the authority for supported elements, operations, enums, limits, canonical Liquid fields, and the finding-page loop pattern on that Vulnotes instance. Construct operations with the typed builders in vulnotes.authoring; these builders reject unknown fields and invalid combinations before a request is sent.

Dry-run the exact batch, then apply it with the same version and operations:

python
checked = client.templates.validate_operations(template_id, version, operations)
if not checked["valid"]:
    for issue in checked["issues"]:
        print(issue["path"], issue["message"])
else:
    saved = client.templates.apply_operations(template_id, version, operations)

An apply is atomic: either every operation succeeds and a revision is recorded, or none of them are saved. A batch contains 1 to 100 operations and is limited to 2 MiB.

contentVersion provides optimistic concurrency. If another writer saves after your read, apply returns HTTP 409 and the SDK raises TemplateVersionConflictError. Its expected_version and current_version attributes describe the conflict. Fetch the authoring document again, reconcile your intended changes, and validate a newly built batch. Never blindly retry stale operations.

Use client.templates.authoring_validation(template_id) to inspect validation issues in the currently stored document after migrations or imports.

Use exact Vulnotes Liquid fields such as client.name, report.title, report.executiveSummary, dates.endDate, scope.description, and stats.criticalCount. Finding detail pages use an explicit loop:

liquid
{% for vuln in vulnerabilities %}
  <h2>{{ vuln.title }}</h2>
  <div>{{ vuln.description }}</div>
  {% unless forloop.last %}{% pagebreak %}{% endunless %}
{% endfor %}

The API dry-run rejects invented roots, misspelled canonical fields, and undeclared custom variable keys.

Uploading and exporting files

Upload methods accept a path, bytes, an open binary file, or a (filename, content) tuple.

python
client.images.upload("evidence.png", report_id=report_id)
client.attachments.upload("nmap-scan.xml", report_id=report_id)

Exports return bytes. Pass path to save the result at the same time:

python
client.reports.export_pdf(report_id, path="report.pdf")
client.reports.export_xlsx(
    report_id,
    finding_fields=["title", "severity"],
    path="findings.xlsx",
)
client.reports.archived_pdf(report_id, path="final.pdf")

archived_pdf() downloads the PDF stored when a report was completed. It raises NotFoundError if the report has no archived PDF.

Handling errors

Every SDK exception inherits from VulnotesError. HTTP errors use more specific classes, so callers can handle only the cases they care about.

python
from vulnotes import NotFoundError, PermissionDeniedError, VulnotesClient

client = VulnotesClient()

try:
    report = client.reports.get(report_id)
except NotFoundError:
    print("The report does not exist or is not visible to this key")
except PermissionDeniedError as error:
    print(f"The API key is missing a permission: {error.message}")

Status errors include status_code, message, body, and the underlying response. Connection and timeout failures use APIConnectionError and APITimeoutError.

Idempotent requests are retried after connection failures and HTTP 429, 502, 503, or 504 responses. The defaults can be changed when creating the client:

python
client = VulnotesClient(
    timeout=60,
    max_retries=5,
)

Set verify_ssl=False only for a local or lab instance with a self-signed certificate.

Calling an endpoint directly

If a new API endpoint is not wrapped by the installed SDK version, call it through the client. Authentication, error handling, timeouts, and response parsing still apply.

python
snapshots = client.request(
    "GET",
    f"/reports/{report_id}/snapshots",
)

The SDK source and release history are available in the vulnotes-python-sdk repository.