Typed models

Every typed model re-exported from pyfsr.models – the shapes returned by the client APIs and the validated argument bundles accepted by the write verbs.

Note

This page is generated from pyfsr.models.__all__. The classes themselves live in private submodules (_playbooks.py, _generated.py, …), which autoapi does not page – so documenting them here under their public name is what gives pyfsr.models.X a resolvable target. Without it every {class}~pyfsr.models.X`` cross-reference in the docs is silently dead.

Records (module entities)

class pyfsr.models.Alert(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, sourceId: str | None = None, source: str | None = None, description: str | None = None, type: PicklistIRI | None = None, severity: PicklistIRI | None = None, status: PicklistIRI | None = None, assignedTo: RecordIRI | User | None = None, dueDate: int | None = None, createUser: str | dict[str, Any] | None = None, modifyUser: RecordIRI | dict[str, Any] | None = None, createDate: float | None = None, modifyDate: float | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An Alert record. Field set is illustrative - 127 properties exist on this entity per the Hydra walk; the ones below are the most-used. Full list via GET /api/3/contexts/Alert.

assignedTo: RecordIRI | User | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
description: str | None[source]
dueDate: int | None[source]
id: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: RecordIRI | dict[str, Any] | None[source]
name: str | None[source]
severity: PicklistIRI | None[source]
source: str | None[source]
sourceId: str | None[source]
status: PicklistIRI | None[source]
type: PicklistIRI | None[source]
class pyfsr.models.Comment(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, attachments: list[Any] | None = None, approvals: list[Any] | None = None, content: str | None = None, tasks: list[Any] | None = None, people: list[Any] | None = None, type: PicklistIRI | None = None, alerts: list[Any] | None = None, isDeleted: str | None = None, assets: list[Any] | None = None, file: str | None = None, file1: str | None = None, campaigns: list[Any] | None = None, file2: str | None = None, rawCommentData: str | None = None, communication: list[Any] | None = None, file3: str | None = None, events: list[Any] | None = None, file4: str | None = None, incidents: list[Any] | None = None, isImportant: bool | None = None, indicators: list[Any] | None = None, peopleUpdated: bool | None = None, replyTo: Any | None = None, warrooms: list[Any] | None = None, devices: list[Any] | None = None, replies: list[Any] | None = None, lastReplyDate: float | None = None, managers: list[Any] | None = None, scenario: list[Any] | None = None, cVEs: list[Any] | None = None, scans: list[Any] | None = None, vulnerabilities: list[Any] | None = None, hunt: list[Any] | None = None, threatActors: list[Any] | None = None, threatIntelReports: list[Any] | None = None, workspaces: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A Comment record. Field set derived from GET /api/3/model_metadatas?$relationships=true. Flags: ownable, taggable.

alerts: list[Any] | None[source]
approvals: list[Any] | None[source]
assets: list[Any] | None[source]
attachments: list[Any] | None[source]
cVEs: list[Any] | None[source]
campaigns: list[Any] | None[source]
communication: list[Any] | None[source]
content: str | None[source]
devices: list[Any] | None[source]
events: list[Any] | None[source]
file: str | None[source]
file1: str | None[source]
file2: str | None[source]
file3: str | None[source]
file4: str | None[source]
hunt: list[Any] | None[source]
incidents: list[Any] | None[source]
indicators: list[Any] | None[source]
isDeleted: str | None[source]
isImportant: bool | None[source]
lastReplyDate: float | None[source]
managers: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

people: list[Any] | None[source]
peopleUpdated: bool | None[source]
rawCommentData: str | None[source]
replies: list[Any] | None[source]
replyTo: Any | None[source]
scans: list[Any] | None[source]
scenario: list[Any] | None[source]
tasks: list[Any] | None[source]
threatActors: list[Any] | None[source]
threatIntelReports: list[Any] | None[source]
type: PicklistIRI | None[source]
vulnerabilities: list[Any] | None[source]
warrooms: list[Any] | None[source]
workspaces: list[Any] | None[source]
class pyfsr.models.Incident(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, responseSLAResumeDate: float | None = None, mitretechniques: list[Any] | None = None, mitresubtechniques: list[Any] | None = None, mitremitigations: list[Any] | None = None, mitregroups: list[Any] | None = None, mitretactics: list[Any] | None = None, mitresoftware: list[Any] | None = None, state: PicklistIRI | None = None, escalated: str | None = None, ticketID: str | None = None, impactROI: int | None = None, wasPersonalDataAffected: PicklistIRI | None = None, warrooms: list[Any] | None = None, incRemainingRespSLA: int | None = None, incRemainingAckSLA: int | None = None, respSLApausedon: float | None = None, ackSLApausedon: float | None = None, volatileData: str | None = None, businessImpact: str | None = None, comments: list[Any] | None = None, companies: list[Any] | None = None, confirmationDate: float | None = None, senderEmailAddress: str | None = None, eradicationDate: float | None = None, filehash: str | None = None, identificationDate: float | None = None, impactAssessments: str | None = None, incidentLead: RecordIRI | User | None = None, incidentsummary: str | None = None, indicators: list[Any] | None = None, metrics: str | None = None, nextsteps: str | None = None, persons: list[Any] | None = None, phase: PicklistIRI | None = None, incidentphase: str | None = None, recoveryDate: float | None = None, resDate: float | None = None, resDueBy: float | None = None, receipientEmailAddress: str | None = None, recoveryTime: int | None = None, resolution: str | None = None, resolveddate: float | None = None, resSla: PicklistIRI | None = None, resPercentSla: int | None = None, senderDomain: str | None = None, severity: PicklistIRI | None = None, sourceId: str | None = None, targetAsset: str | None = None, tasks: list[Any] | None = None, category: PicklistIRI | None = None, ackDueDate: float | None = None, responseDate: float | None = None, otherLogs: str | None = None, siemQuery: str | None = None, fileName: str | None = None, name: str | None = None, alerts: list[Any] | None = None, assets: list[Any] | None = None, campaigns: list[Any] | None = None, communications: list[Any] | None = None, mitreattackid: str | None = None, c2server: str | None = None, dLLName: str | None = None, processName: str | None = None, affectedUser: str | None = None, affectedHost: str | None = None, pcapFile: str | None = None, ackDate: float | None = None, slaState: PicklistIRI | None = None, slaPercentage: int | None = None, aftermathDate: float | None = None, assigneddate: float | None = None, attachments: list[Any] | None = None, containmentDate: float | None = None, containmentTime: int | None = None, dateOfIncident: float | None = None, deliveryVector: PicklistIRI | None = None, description: str | None = None, destinationIP: str | None = None, deviceUID: str | None = None, discoveredOn: float | None = None, dwellTime: int | None = None, source: str | None = None, sourcedata: str | None = None, sourceIP: str | None = None, cVEs: list[Any] | None = None, status: PicklistIRI | None = None, vulnerabilities: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An Incident record. Field set derived from GET /api/3/model_metadatas?$relationships=true. Unique constraints: [{‘incidents_unique’: {‘columns’: [‘sourceId’, ‘tenant’]}}]. Flags: taggable, queueable.

ackDate: float | None[source]
ackDueDate: float | None[source]
ackSLApausedon: float | None[source]
affectedHost: str | None[source]
affectedUser: str | None[source]
aftermathDate: float | None[source]
alerts: list[Any] | None[source]
assets: list[Any] | None[source]
assigneddate: float | None[source]
attachments: list[Any] | None[source]
businessImpact: str | None[source]
c2server: str | None[source]
cVEs: list[Any] | None[source]
campaigns: list[Any] | None[source]
category: PicklistIRI | None[source]
comments: list[Any] | None[source]
communications: list[Any] | None[source]
companies: list[Any] | None[source]
confirmationDate: float | None[source]
containmentDate: float | None[source]
containmentTime: int | None[source]
dLLName: str | None[source]
dateOfIncident: float | None[source]
deliveryVector: PicklistIRI | None[source]
description: str | None[source]
destinationIP: str | None[source]
deviceUID: str | None[source]
discoveredOn: float | None[source]
dwellTime: int | None[source]
eradicationDate: float | None[source]
escalated: str | None[source]
fileName: str | None[source]
filehash: str | None[source]
identificationDate: float | None[source]
impactAssessments: str | None[source]
impactROI: int | None[source]
incRemainingAckSLA: int | None[source]
incRemainingRespSLA: int | None[source]
incidentLead: RecordIRI | User | None[source]
incidentphase: str | None[source]
incidentsummary: str | None[source]
indicators: list[Any] | None[source]
metrics: str | None[source]
mitreattackid: str | None[source]
mitregroups: list[Any] | None[source]
mitremitigations: list[Any] | None[source]
mitresoftware: list[Any] | None[source]
mitresubtechniques: list[Any] | None[source]
mitretactics: list[Any] | None[source]
mitretechniques: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
nextsteps: str | None[source]
otherLogs: str | None[source]
pcapFile: str | None[source]
persons: list[Any] | None[source]
phase: PicklistIRI | None[source]
processName: str | None[source]
receipientEmailAddress: str | None[source]
recoveryDate: float | None[source]
recoveryTime: int | None[source]
resDate: float | None[source]
resDueBy: float | None[source]
resPercentSla: int | None[source]
resSla: PicklistIRI | None[source]
resolution: str | None[source]
resolveddate: float | None[source]
respSLApausedon: float | None[source]
responseDate: float | None[source]
responseSLAResumeDate: float | None[source]
senderDomain: str | None[source]
senderEmailAddress: str | None[source]
severity: PicklistIRI | None[source]
siemQuery: str | None[source]
slaPercentage: int | None[source]
slaState: PicklistIRI | None[source]
source: str | None[source]
sourceIP: str | None[source]
sourceId: str | None[source]
sourcedata: str | None[source]
state: PicklistIRI | None[source]
status: PicklistIRI | None[source]
targetAsset: str | None[source]
tasks: list[Any] | None[source]
ticketID: str | None[source]
volatileData: str | None[source]
vulnerabilities: list[Any] | None[source]
warrooms: list[Any] | None[source]
wasPersonalDataAffected: PicklistIRI | None[source]
class pyfsr.models.Task(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, submittedBy: RecordIRI | User | None = None, name: str | None = None, description: str | None = None, type: PicklistIRI | None = None, dueBy: float | None = None, assignedOnDate: float | None = None, startDate: float | None = None, completedOnDate: float | None = None, actualMinutes: int | None = None, priority: PicklistIRI | None = None, status: PicklistIRI | None = None, assignedToPerson: RecordIRI | User | None = None, companies: list[Any] | None = None, persons: list[Any] | None = None, alerts: list[Any] | None = None, attachments: list[Any] | None = None, assets: list[Any] | None = None, comments: list[Any] | None = None, incidents: list[Any] | None = None, indicators: list[Any] | None = None, warrooms: list[Any] | None = None, approvalhost: str | None = None, cVEs: list[Any] | None = None, vulnerabilities: list[Any] | None = None, workflowid: str | None = None, taskdata: str | None = None, tasktype: str | None = None, hunt: list[Any] | None = None, stepid: int | None = None, threatIntelFeeds: list[Any] | None = None, workspaces: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A Task record. Field set derived from GET /api/3/model_metadatas?$relationships=true. Flags: ownable, taggable, queueable.

actualMinutes: int | None[source]
alerts: list[Any] | None[source]
approvalhost: str | None[source]
assets: list[Any] | None[source]
assignedOnDate: float | None[source]
assignedToPerson: RecordIRI | User | None[source]
attachments: list[Any] | None[source]
cVEs: list[Any] | None[source]
comments: list[Any] | None[source]
companies: list[Any] | None[source]
completedOnDate: float | None[source]
description: str | None[source]
dueBy: float | None[source]
hunt: list[Any] | None[source]
incidents: list[Any] | None[source]
indicators: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
persons: list[Any] | None[source]
priority: PicklistIRI | None[source]
startDate: float | None[source]
status: PicklistIRI | None[source]
stepid: int | None[source]
submittedBy: RecordIRI | User | None[source]
taskdata: str | None[source]
tasktype: str | None[source]
threatIntelFeeds: list[Any] | None[source]
type: PicklistIRI | None[source]
vulnerabilities: list[Any] | None[source]
warrooms: list[Any] | None[source]
workflowid: str | None[source]
workspaces: list[Any] | None[source]

Integrations & connectors

class pyfsr.models.ApiResult(**extra_data: Any)[source]

Bases: BaseModel

Dict-compatible base for typed API result shapes.

Subclasses get attribute access (r.config_id) and dict-style subscripting (r["config_id"]), so callers don’t need to migrate all at once. Unknown fields from the wire are preserved under extra.

get(key: str, default: Any = None) → Any[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to_dict(*, by_alias: bool = True, exclude_none: bool = False) → dict[str, Any][source]
class pyfsr.models.ConfigValidationError(*, field: str | None = None, code: str | None = None, message: str | None = None, valid_options: list[Any] | None = None, expected: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A single field-level error from validate_config().

code: str | None[source]
expected: str | None[source]
field: str | None[source]
message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

valid_options: list[Any] | None[source]
class pyfsr.models.ConfigValidationResult(*, valid: bool = False, missing: list[str] = <factory>, invalid: list[str] = <factory>, unknown: list[str] = <factory>, errors: list[ConfigValidationError] = <factory>, **extra_data: Any)[source]

Bases: ApiResult

Return value of client.connectors.validate_config().

valid is True only when missing and invalid are both empty. unknown fields are reported but do not make the config invalid.

errors: list[ConfigValidationError][source]
invalid: list[str][source]
missing: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

unknown: list[str][source]
valid: bool[source]
class pyfsr.models.ConnectorConfig(*, id: int | None = None, config_id: str | None = None, name: str | None = None, default: bool = False, status: int | None = None, config: dict[str, ~typing.Any]=<factory>, connector: int | None = None, agent: str | None = None, teams: list[Any] = <factory>, remote_status: dict[str, ~typing.Any]=<factory>, health_status: dict[str, ~typing.Any]=<factory>, **extra_data: Any)[source]

Bases: ApiResult

A connector configuration record from /api/integration/configuration/.

Returned by create_configuration(), update_configuration(), and list_configurations(). config is the live field map – its shape varies by connector.

agent: str | None[source]
config: dict[str, Any][source]
config_id: str | None[source]
connector: int | None[source]
default: bool[source]
health_status: dict[str, Any][source]
id: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
remote_status: dict[str, Any][source]
status: int | None[source]
teams: list[Any][source]
class pyfsr.models.ConnectorConfigSummary(*, id: int | None = None, config_id: str | None = None, name: str | None = None, default: bool = False, **extra_data: Any)[source]

Bases: ApiResult

A single configuration entry embedded in the connector listing.

From /api/integration/connectors/ configuration[].

config_id: str | None[source]
default: bool[source]
id: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
class pyfsr.models.ConnectorDefinition(*, name: str | None = None, version: str | None = None, label: str | None = None, description: str | None = None, publisher: str | None = None, category: str | list[str] | None = None, active: bool | None = None, cs_approved: bool | None = None, cs_compatible: bool | None = None, operations: list[Operation] = <factory>, config_schema: ConfigSchema = <factory>, configuration: Any = None, **extra_data: Any)[source]

Bases: ApiResult

A connector’s full definition (config schema + operations).

Returned by definition() – the POST /api/integration/connectors/<name>/<version>/?format=json payload warm_catalog reads to sync the installed connector catalog. category may arrive as a string or a list; both are tolerated. Curated fields are typed; config_schema/configuration stay loose (shape varies by connector). Dict-compatible.

active: bool | None[source]
category: str | list[str] | None[source]
config_schema: ConfigSchema[source]
configuration: Any[source]
cs_approved: bool | None[source]
cs_compatible: bool | None[source]
description: str | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
operations: list[Operation][source]
publisher: str | None[source]
version: str | None[source]
class pyfsr.models.DependencyStatus(*, dependencies_installed: bool | None = None, message: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

Python-dependency state for an installed connector.

From GET /api/integration/connectors/dependencies_check/<name>/<version>/. The UI maps dependencies_installed onto the tri-state badge Completed / Failed / In-Progress shown on the connector card.

dependencies_installed: bool | None[source]
message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.EnsureVersionResult(*, action: str | None = None, from_version: str | None = None, to: str | None = None, backup: str | None = None, configs_before: int = 0, configs_after: int = 0, **extra_data: Any)[source]

Bases: ApiResult

Return value of client.connectors.ensure_version().

action is one of "noop", "in_place", "restored", "reinstalled", or "failed".

action: str | None[source]
backup: str | None[source]
configs_after: int[source]
configs_before: int[source]
from_version: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to: str | None[source]
class pyfsr.models.ExecuteResult(*, operation: str | None = None, status: str | None = None, message: str | None = None, data: Any = None, **extra_data: Any)[source]

Bases: ApiResult

Return value of client.connectors.execute().

data is the connector’s own output – its shape varies by connector and operation.

data: Any[source]
message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property ok: bool[source]

True when the connector reported success (status == "Success").

Saves callers the recurring str(r.status).lower() == "success" check. Note (see ConnectorsAPI.execute): an agent-bound, fire-and-forget call can succeed with empty data – ok reflects status, not data.

operation: str | None[source]
status: str | None[source]
class pyfsr.models.ExportJobResult(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, id: int | None = None, status: str | None = None, errorMessage: str | None = None, fileName: str | None = None, progressPercent: int | None = None, currentlyExporting: str | None = None, type: str | None = None, file: RecordIRI | dict[str, Any] | None = None, **extra_data: Any)[source]

Bases: ApiResult

A /api/3/export_jobs record.

Returned by export polling in export_config. status == "Export Complete" means the archive is ready for download. file is the /api/3/files/<uuid> record (or its IRI string) once the export finishes.

currentlyExporting: str | None[source]
errorMessage: str | None[source]
file: RecordIRI | dict[str, Any] | None[source]
fileName: str | None[source]
id: int | None[source]
id_iri: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

progressPercent: int | None[source]
record_type: str | None[source]
status: str | None[source]
type: str | None[source]
uuid: str | None[source]
class pyfsr.models.HealthcheckResult(*, status: str | None = None, message: str | None = None, name: str | None = None, version: str | None = None, config_id: str | None = None, request_id: str | None = None, http_status: int | None = None, _status: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

Return value of client.connectors.healthcheck().

status == "Available" is green. status == "no-config" means the connector isn’t configured on this instance (pyfsr-synthesised, not from the wire).

config_id: str | None[source]
http_status: int | None[source]
message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
ok: bool | None[source]
request_id: str | None[source]
status: str | None[source]
version: str | None[source]
class pyfsr.models.ImportJobResult(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, id: int | None = None, status: str | None = None, errorMessage: str | None = None, logMessages: list[LogMessage] = <factory>, options: dict[str, ~typing.Any] | list=<factory>, file: RecordIRI | dict[str, ~typing.Any] | None=None, jobUuid: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A /api/3/import_jobs record.

Returned by import_config.import_file() and the lower-level job polling methods. status == "Import Complete" means success. options is the server-generated import option tree (section → include flags).

errorMessage: str | None[source]
file: RecordIRI | dict[str, Any] | None[source]
id: int | None[source]
id_iri: str | None[source]
jobUuid: str | None[source]
logMessages: list[LogMessage][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

options: dict[str, Any] | list[source]
record_type: str | None[source]
status: str | None[source]
uuid: str | None[source]
class pyfsr.models.IngestionMetadata(*, id: int | None = None, name: str | None = None, description: str | None = None, configuration: str | None = None, connector: dict[str, ~typing.Any]=<factory>, metadata: dict[str, ~typing.Any]=<factory>, sample_data: Any = None, owners: list[Any] = <factory>, created_by: str | None = None, modified_by: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A /api/integration/data-import/ record.

The join between a connector configuration and the periodic task that drives its ingestion. The UI writes one of these per configuration and later re-finds the schedule through metadata.scheduleId – without it the Configure Data Ingestion screen cannot show an existing schedule.

configuration: str | None[source]
connector: dict[str, Any][source]
created_by: str | None[source]
description: str | None[source]
id: int | None[source]
metadata: dict[str, Any][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified_by: str | None[source]
name: str | None[source]
owners: list[Any][source]
sample_data: Any[source]
property schedule_id: str | None[source]

The periodic-task id this configuration’s ingestion runs under.

class pyfsr.models.IngestionPlaybooks(*, fetch: Workflow | None = None, ingest: Workflow | None = None, create: Workflow | None = None, update: Workflow | None = None, **extra_data: Any)[source]

Bases: ApiResult

The tag-bucketed ingestion playbooks for one connector.

FortiSOAR identifies ingestion playbooks purely by record tag: a playbook tagged fetch is the sample-data fetcher, ingest is the one the schedule fires, create/update are the record writers. A single playbook can carry several of these tags at once (FortiSIEM’s FortiSIEM > Ingest is tagged ingest and create).

create: Workflow | None[source]
fetch: Workflow | None[source]
ingest: Workflow | None[source]
missing() → list[str][source]

Which of the four ingestion roles have no playbook.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

update: Workflow | None[source]
class pyfsr.models.IngestionSetupResult(*, connector: str | None = None, version: str | None = None, config_id: str | None = None, config_name: str | None = None, collection_uuid: str | None = None, collection_name: str | None = None, playbooks: list[Workflow] = <factory>, ingest_playbook_iri: str | None = None, schedule_id: str | None = None, schedule_name: str | None = None, scheduled: bool = False, health_status: str | None = None, cloned: bool = False, dry_run: bool = False, existed: bool = False, **extra_data: Any)[source]

Bases: ApiResult

What data_ingest_wizard() built.

Mirrors the end state of the UI wizard: a per-configuration playbook collection, the cloned+rewritten ingestion playbooks inside it, the periodic task that fires the ingest playbook, and the data-import metadata record that ties them together.

cloned: bool[source]
collection_name: str | None[source]
collection_uuid: str | None[source]
config_id: str | None[source]
config_name: str | None[source]
connector: str | None[source]
dry_run: bool[source]
existed: bool[source]

True when ensure_ingestion() found ingestion already configured and returned it without writing (“get”); False when the wizard actually built it (“make”).

health_status: str | None[source]
ingest_playbook_iri: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

playbooks: list[Workflow][source]
schedule_id: str | None[source]
schedule_name: str | None[source]
scheduled: bool[source]
version: str | None[source]
class pyfsr.models.IngestionStatus(*, connector: str | None = None, config_id: str | None = None, collection_exists: bool = False, playbooks: IngestionPlaybooks = <factory>, metadata: IngestionMetadata | None = None, schedule_id: str | None = None, schedule_name: str | None = None, schedule_enabled: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

The current data-ingestion state of one connector configuration.

The read-only counterpart to data_ingest_wizard(): it inspects what the wizard would have built – the per-configuration collection, the cloned ingestion playbooks, the periodic task, and the data-import metadata record – and reports whether each piece is present, without writing anything.

collection_exists: bool[source]

True when the per-configuration ingestion collection exists.

config_id: str | None[source]
property configured: bool[source]

Whether ingestion has been set up at all for this configuration.

True once the collection exists and holds an ingest-tagged playbook – the minimum the Trigger Ingestion Now button needs. A configured setup may still be unscheduled (schedule_id is None).

connector: str | None[source]
metadata: IngestionMetadata | None[source]

data-import metadata record for this config, if one was written.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

playbooks: IngestionPlaybooks[source]

The ingestion playbooks found in that collection, bucketed by role.

schedule_enabled: bool | None[source]

True when a schedule exists and is enabled; None when unknown (no schedule referenced by the metadata record).

schedule_id: str | None[source]
schedule_name: str | None[source]
class pyfsr.models.IngestionTeardownResult(*, connector: str | None = None, config_id: str | None = None, schedule_name: str | None = None, schedule_deleted: bool = False, metadata_ids: list[int] = <factory>, metadata_deleted: int = 0, collection_uuid: str | None = None, collection_deleted: bool = False, dry_run: bool = False, **extra_data: Any)[source]

Bases: ApiResult

What remove_ingestion() removed.

The inverse of IngestionSetupResult: it records which of the wizard’s four artifacts were torn down – the periodic task, the data-import metadata record(s), and the per-configuration collection (which cascades the cloned playbooks). With dry_run=True the *_deleted flags/counts stay False/0 and the fields report what would be removed.

collection_deleted: bool[source]
collection_uuid: str | None[source]
config_id: str | None[source]
connector: str | None[source]
dry_run: bool[source]
metadata_deleted: int[source]
metadata_ids: list[int][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

schedule_deleted: bool[source]
schedule_name: str | None[source]
class pyfsr.models.InstallJobStatus(*, status: str | None = None, progressPercent: int | None = None, errorMessage: str | None = None, currentlyImporting: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

Progress record for a connector install import job.

Returned by install_status() and wait_for_install(). status == "Import Complete" means the install finished successfully.

currentlyImporting: str | None[source]
errorMessage: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

progressPercent: int | None[source]
status: str | None[source]
class pyfsr.models.InstalledConnector(*, id: int | None = None, name: str | None = None, version: str | None = None, label: str | None = None, active: bool | None = None, system: bool | None = None, config_count: int | None = None, status: str | None = None, configuration: list[ConnectorConfigSummary] = <factory>, ingestion_supported: bool | None = None, tags: list[Any] = <factory>, agent: str | None = None, development: bool | None = None, created: str | None = None, modified: str | None = None, publisher: str | None = None, contributor: str | None = None, rpm_installed: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

An installed connector entry from GET /api/integration/connectors/.

Only the fields that are stable and useful for code are typed; the rest (icons, descriptions, help links) live in extra.

active: bool | None[source]
agent: str | None[source]
config_count: int | None[source]
configurations: list[ConnectorConfigSummary][source]
contributor: str | None[source]
created: str | None[source]
development: bool | None[source]
id: int | None[source]
ingestion_supported: bool | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified: str | None[source]
name: str | None[source]
publisher: str | None[source]
rpm_installed: bool | None[source]
status: str | None[source]
system: bool | None[source]
tags: list[Any][source]
version: str | None[source]
class pyfsr.models.IntegrationListEnvelope(*, status: str | None = None, totalItems: int | None = None, itemsPerPage: int | None = None, nextPage: int | None = None, previousPage: int | None = None, data: list[Any] = <factory>, **extra_data: Any)[source]

Bases: ApiResult

The custom (non-Hydra) list envelope several /api/integration endpoints return.

Unlike the JSON-LD collection wrapped by HydraPage (hydra:member/hydra:totalItems), endpoints like GET /api/integration/connectors/ and GET /api/integration/configuration/ page with a plain envelope:

{"status": "...", "totalItems": 73, "itemsPerPage": 30,
 "nextPage": 2, "previousPage": null, "data": [ {...}, ... ]}

Typed once here so callers parse it the same way everywhere (it has been mis-read as a bare list more than once). data stays list[Any] – the per-endpoint method validates each row into its own model.

data: list[Any][source]
property has_next: bool[source]

Whether the envelope advertises a further page (nextPage set).

itemsPerPage: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

nextPage: int | None[source]
classmethod parse(response: Any) → IntegrationListEnvelope[source]

Coerce a raw response into an envelope, tolerating a bare list/None.

A dict is validated as the envelope; a bare list is wrapped as its data (some endpoints/versions return the array directly); anything else yields an empty envelope.

previousPage: int | None[source]
status: str | None[source]
totalItems: int | None[source]
class pyfsr.models.LogMessage(*, message: str | None = None, date: int | None = None, **extra_data: Any)[source]

Bases: ApiResult

A single entry from an import job’s logMessages list.

date: int | None[source]
message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.Operation(*, operation: str | None = None, title: str | None = None, description: str | None = None, annotation: str | None = None, category: str | None = None, visible: bool = True, enabled: bool = True, parameters: list[OperationParam] = <factory>, output_schema: Any = None, **extra_data: Any)[source]

Bases: ApiResult

One action a connector exposes, from its definition’s operations[].

Richer than ConnectorOperation (the Content-Hub catalog shape) – this is the runtime definition, carrying typed OperationParam inputs. visible/enabled default to True when omitted. Dict-compatible (op["operation"] still works).

annotation: str | None[source]
category: str | None[source]
description: str | None[source]
enabled: bool[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operation: str | None[source]
output_schema: Any[source]
parameters: list[OperationParam][source]
title: str | None[source]
ui_params(*, required_only: bool = False, selections: dict[str, Any] | None = None) → list[OperationParam][source]

The params a UI/agent must render to stage this action, ordered.

Returns the operation’s parameters filtered to the visible ones (hidden params are platform-managed, not author-facing), ordered required-first then by declared order, and deduped by name – a param that appears in several conditional onchange groups (e.g. block_ip’s ip under each method option) collapses to its first occurrence, so the list reads as one form, not a repeated field.

This is the schema every action-staging caller was re-deriving by hand (the fsr-playbook-framework MCP discovery tool’s _param_sig / _required_params). Each returned OperationParam still carries its type/title/required and – for a select – its select_options(), so the caller picks valid param names and valid choice values straight from the definition.

Conditional reveal (``selections``). A select param can gate further inputs via its onchange map (option value → the sub-params that become active when it is chosen). By default those sub-params are not returned – the base form only. Pass selections (a {param_name: chosen_value} map of what the user has picked so far) to also include the sub-params those choices reveal, so you render only the fields actually needed for the current state. On box 206, choosing smtp/send_email_new’s type reveals to/cc/bcc:

op.ui_params()                              # base params only
op.ui_params(selections={"type": "Team"})   # base + to, cc, bcc

Reveal is recursive (a revealed sub-param may itself be a gating select) and matches a selection against the onchange keys by the value or its string form. Revealed params fold into the same visible/required-first/deduped result. Unrecognized selections (unknown param, or a value with no matching branch) are ignored.

Pass required_only=True to keep just the required params.

visible: bool[source]
class pyfsr.models.OperationParam(*, name: str | None = None, title: str | None = None, type: str | None = None, description: str | None = None, tooltip: str | None = None, placeholder: str | None = None, required: bool = False, value: Any = None, visible: bool = True, editable: bool = True, onchange: dict[str, list[~pyfsr.models._integration.OperationParam]]=<factory>, options: list[Any] = <factory>, apiOperation: str | None = None, apiOnchange: bool = False, **extra_data: Any)[source]

Bases: ApiResult

One input parameter of a connector operation, from a connector definition.

The parameters[] of an operation in POST /api/integration/connectors/<name>/<version>/?format=json. value is the declared default (its type varies by field). visible/editable default to True when the wire omits them. onchange is typed recursively (dict[str, list[OperationParam]]) so conditional sub-params validate too; options stays list[Any] (plain strings or {value,title} dicts).

Dynamic dropdowns: apiOperation names a sibling connector operation whose result populates the choices at render time (e.g. get_incident_severities); apiOnchange=True re-calls it whenever a sibling field changes (cascading dropdowns). See the Connector Building Guide section “Dynamic Options from an Operation”.

apiOnchange: bool[source]
apiOperation: str | None[source]
description: str | None[source]
editable: bool[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
onchange: dict[str, list[OperationParam]][source]
options: list[Any][source]
placeholder: str | None[source]
required: bool[source]
select_options() → list[ParamOption][source]

The param’s choices as normalized ParamOptions.

Empty for a non-select param. Tolerates both wire shapes: a bare string "Basic" yields ParamOption(value="Basic", title="Basic"); a {"value","title"} dict maps across, defaulting a missing title to str(value) (and a missing value to title) so a caller always has both a send-value and a label.

title: str | None[source]
tooltip: str | None[source]
type: str | None[source]
value: Any[source]
visible: bool[source]
class pyfsr.models.ParamOption(*, value: Any = None, title: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

One choice of a select/multiselect connector-operation parameter.

Normalized from the two wire shapes an operation’s options[] uses (see OperationParam.select_options()): a plain string "Basic" becomes ParamOption(value="Basic", title="Basic"); a {"value": ..., "title": ...} dict maps straight across (either key may be absent – the other fills in). value is what you send as the param; title is the label a UI shows.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

title: str | None[source]
value: Any[source]

Playbooks & runs

class pyfsr.models.ApprovalRequest(*, decision: str, comment: str | None = None)[source]

Bases: _RequestModel

Typed body for approval().

comment: str | None[source]
decision: str[source]
model_config = {'extra': 'forbid'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to_body() → dict[str, Any][source]

Render the approval-decision JSON body.

class pyfsr.models.CreatePlaybookRequest(*, name: str, collection: str, is_active: bool = True, remote_executable: bool = False, priority: str | None = None, origin: str | None = None, **extra_data: Any)[source]

Bases: _RequestModel

Typed body for create_playbook().

Deliberately shallow: it validates the playbook-definition envelope (name / collection / flags / picklist IRIs) and passes any other fields through verbatim. The deep step/route shape is owned by the fsr_playbooks compiler, not this model.

collection: str[source]
is_active: bool[source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str[source]
origin: str | None[source]
priority: str | None[source]
remote_executable: bool[source]
to_body() → dict[str, Any][source]

Render the playbook-definition JSON body, expanding the collection to an IRI.

class pyfsr.models.CreateVersionRequest(*, workflow: str, json: str, note: str = '', modify_date: int | None = None)[source]

Bases: _RequestModel

Typed body for create_version().

Mirrors FortiSOAR’s editor saveSnapshot wire: json is the prepared workflow stringified, workflow is the workflow IRI, modifyDate is an epoch second timestamp. note labels the snapshot.

model_config = {'extra': 'forbid'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modify_date: int | None[source]
note: str[source]
snapshot_json: str[source]
to_body() → dict[str, Any][source]

Render the snapshot-create JSON body (the editor’s Q() shape).

workflow: str[source]
class pyfsr.models.PlaybookVersion(*, note: str | None = None, autosave: bool | None = None, uuid: str | None = None, json: str | None = None, workflow: Any | None = None, createDate: float | None = None, modifyDate: float | None = None, **extra_data: Any)[source]

Bases: ApiResult

One saved playbook snapshot (the workflow_versions module).

FortiSOAR’s playbook “version control” is a snapshot history, not a revision/diff resource: each version is a frozen copy of the playbook stored under /api/3/workflow_versions (capped at 20 per playbook). A version is either a manual snapshot (autosave=False, a caller-supplied note) or an editor auto-save (autosave=True).

json is the snapshot payload – the full workflow definition stringified (steps / routes / groups / triggerStep / …). It is populated on list_versions / get_version but not echoed back by create_version (the server omits the blob on the POST response); fetch the version again to read json. workflow is the embedded workflow the snapshot belongs to.

autosave: bool | None[source]
create_date: float | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modify_date: float | None[source]
note: str | None[source]
parsed_json() → dict[str, Any][source]

Decode the snapshot’s json field into the workflow dict.

Raises ValueError if json is absent (e.g. a create_version response, which does not echo the blob) – call get_version first.

property snapshot: str | None[source]

The snapshot payload (stringified workflow). Wire field json.

snapshot_json: str | None[source]
uuid: str | None[source]
workflow: Any | None[source]
property workflow_iri: str | None[source]

The snapshot’s workflow @id (the playbook it belongs to).

class pyfsr.models.ResumeRequest(*, manual_input_id: int, input: Any = None, step_iri: str | None = None, step_id: str | None = None, approved: bool | None = None)[source]

Bases: _RequestModel

Typed body for resume() (manual-input / approval resume).

approved: bool | None[source]
input: Any[source]
manual_input_id: int[source]
model_config = {'extra': 'forbid'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

step_id: str | None[source]
step_iri: str | None[source]
to_body() → dict[str, Any][source]

Render the manual-input/approval resume JSON body.

class pyfsr.models.RunEnv(*, name: str | None = None, env: dict[str, ~typing.Any]=<factory>, status: str | None = None, steps: dict[str, ~pyfsr.models._playbooks.RunStep]=<factory>, **extra_data: Any)[source]

Bases: ApiResult

A run’s Jinja-context view, from run_env().

env is the run’s top-level environment (input/request/resources/…); steps is keyed by step display name. In Jinja a step is referenced as vars.steps.<name with spaces replaced by underscores>. name is the run’s playbook display name (handy for pulling the live playbook back).

env: dict[str, Any][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
status: str | None[source]
steps: dict[str, RunStep][source]
class pyfsr.models.RunFailure(*, status: str | None = None, failing_step: str | None = None, error_message: str | None = None, pk: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The slim failure projection from why_failed().

failing_step is the display name of the first non-success step (None if the run succeeded); error_message is the step-level error when present, else the run’s top-level error.

error_message: str | None[source]
failing_step: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

pk: str | None[source]
status: str | None[source]
class pyfsr.models.RunNode(*, pk: str | None = None, name: str | None = None, status: str | None = None, task_id: str | None = None, children: list[RunNode] = <factory>, steps: list[RunStepSnapshot] = <factory>, **extra_data: Any)[source]

Bases: ApiResult

One node in a run tree, from run_tree().

The run plus its referenced-child runs (linked by parent_wf), recursively. pk is the numeric run id; children are the sub-playbook runs this run spawned. Encodes the trigger->run->child linkage so callers don’t have to find the parent by name in the raw /api/wf/api/workflows listing.

steps carries a slim per-step snapshot (name/status/result_preview) on the root node when run_tree(steps=True); empty otherwise (and always empty on child nodes). Call run_env() for a child’s full step detail.

children: list[RunNode][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
pk: str | None[source]
status: str | None[source]
steps: list[RunStepSnapshot][source]
task_id: str | None[source]
class pyfsr.models.RunResult(*, status: str | None = None, task_id: str | None = None, pk: str | None = None, name: str | None = None, steps: list[RunStepSnapshot] = <factory>, failure: RunFailure | None = None, children: list[RunResult] = <factory>, **extra_data: Any)[source]

Bases: ApiResult

The typed result of run_and_wait().

The all-in-one outcome of a trigger-and-poll cycle: the run’s terminal status, per-step snapshots (with timing), failure details, and child-run results (for sub-playbook chains). Everything an agent needs to debug a playbook in one object.

children: list[RunResult][source]
failure: RunFailure | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
pk: str | None[source]
property slow_steps: list[RunStepSnapshot][source]

Steps flagged slow by each step’s own RunStepSnapshot.slow_threshold_ms (default 30s).

status: str | None[source]
steps: list[RunStepSnapshot][source]
property succeeded: bool[source]

True when status == "finished".

task_id: str | None[source]
class pyfsr.models.RunStep(*, status: str | None = None, result: Any | None = None, start_time: str | None = None, end_time: str | None = None, duration_ms: int | None = None, slow_threshold_ms: int = 30000, **extra_data: Any)[source]

Bases: ApiResult

One step’s outcome within a run, as reshaped by run_env().

Timing fields (start_time / end_time / duration_ms) are parsed from the wire’s started / completed ISO timestamps – available when step_detail=True (which run_env() always sets).

duration_ms: int | None[source]
end_time: str | None[source]
property is_slow: bool[source]

True when duration_ms exceeds slow_threshold_ms (default 30s).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

result: Any | None[source]
slow_threshold_ms: int[source]

Threshold is_slow compares against. Excluded from serialization – it is a client-side view setting, not a field the appliance sends.

start_time: str | None[source]
status: str | None[source]
class pyfsr.models.RunStepSnapshot(*, name: str | None = None, status: str | None = None, result_preview: str | None = None, start_time: str | None = None, end_time: str | None = None, duration_ms: int | None = None, slow_threshold_ms: int = 30000, **extra_data: Any)[source]

Bases: ApiResult

A slim per-step outcome snapshot for run_tree() (steps=True).

A trimmed preview of a step’s result – enough for an agent to decide whether to call run_env() for the full detail, without the full result bloating the tree. result_preview is the step’s result JSON-encoded and capped to ~500 chars.

Timing fields (start_time / end_time / duration_ms) are parsed from the wire’s started / completed ISO timestamps.

duration_ms: int | None[source]
end_time: str | None[source]
property is_slow: bool[source]

True when duration_ms exceeds slow_threshold_ms (default 30s).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
result_preview: str | None[source]
slow_threshold_ms: int[source]

Threshold is_slow compares against. Excluded from serialization – it is a client-side view setting, not a field the appliance sends.

start_time: str | None[source]
status: str | None[source]
class pyfsr.models.RunSummary(*, task_id: str | None = None, name: str | None = None, status: str | None = None, error_message: str | None = None, modified: str | None = None, uuid: str | None = None, pk: str | None = None, source: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A flattened playbook-run summary (the default PlaybooksAPI view).

Produced by _shape_run for execution_history(), last_run(), wait(), and friends. pk is the trailing segment of the run’s @id (what get_execution() takes); source is "live" or "historical" (which run table it came from).

error_message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified: str | None[source]
name: str | None[source]
pk: str | None[source]
source: str | None[source]
status: str | None[source]
task_id: str | None[source]
uuid: str | None[source]
class pyfsr.models.TriggerActionRequest(*, module: str, record_uuid: str, playbook_uuid: str | None = None, env: dict[str, ~typing.Any]=<factory>)[source]

Bases: _RequestModel

Typed body for trigger_action() (the record-action / cybersponse.action trigger route).

env: dict[str, Any][source]
model_config = {'extra': 'forbid'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str[source]
playbook_uuid: str | None[source]
record_uuid: str[source]
to_body() → dict[str, Any][source]

Render the single-record action-trigger JSON body.

class pyfsr.models.TriggerRequest(*, records: list[str] = <factory>, inputs: dict[str, ~typing.Any] | None=None, env: dict[str, ~typing.Any]=<factory>)[source]

Bases: _RequestModel

Typed body for trigger().

records accepts a single ref or a list; bare uuids/refs are expanded to /api/3/alerts/<uuid> IRIs. env keys are merged into the body verbatim for the rare playbook expecting a custom trigger envelope.

env: dict[str, Any][source]
inputs: dict[str, Any] | None[source]
model_config = {'extra': 'forbid'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

records: list[str][source]
to_body() → dict[str, Any][source]

Render the request as the JSON body FortiSOAR’s trigger route expects.

class pyfsr.models.TriggerResponse(*, task_id: str | list[str] | None = None, **extra_data: Any)[source]

Bases: ApiResult

The response from a trigger verb (trigger / trigger_by_name / trigger_action).

Normally {"task_id": "<run-uuid>"}, but a trigger that starts more than one run (e.g. an API-endpoint route bound to several playbooks) returns task_id as a list of run-uuids – so this accepts either. Extra keys (e.g. a deferred 202 envelope) are preserved. Use task_ids for a uniform list, or task_id to track the started run with wait().

The routes do not agree on the key. Live-verified on the record-action route (/api/triggers/1/action/<route>): it answers {"task_ids": [...]} – plural – where the manual-execute route (notrigger) answers {"task_id": "..."}. Because only task_id was declared, a wire task_ids used to land in model_extra while the task_ids property (which normalizes task_id) shadowed it and returned [] – so trigger_action callers could not reach the run they had just started through either accessor. _absorb_plural_task_ids folds the plural wire key into task_id before validation, making both accessors work for both routes and honouring this docstring’s “task_id may be a list” contract.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

task_id: str | list[str] | None[source]
property task_ids: list[str][source]

task_id normalized to a list (empty when absent).

class pyfsr.models.VersionDiff(*, added: list[str] = <factory>, removed: list[str] = <factory>, changed: list[VersionStepDelta] = <factory>, routes_added: list[str] = <factory>, routes_removed: list[str] = <factory>, groups_added: list[str] = <factory>, groups_removed: list[str] = <factory>, **extra_data: Any)[source]

Bases: ApiResult

A step-graph diff between two playbook snapshots (diff_versions()).

Steps are keyed by uuid. added / removed are step uuids present in only one side; changed holds per-step field deltas. routes / groups are the simpler added/removed-uuid lists for those graphs.

added: list[str][source]
changed: list[VersionStepDelta][source]
groups_added: list[str][source]
groups_removed: list[str][source]
property is_clean: bool[source]

True when the two snapshots are identical (no added/removed/changed).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

removed: list[str][source]
routes_added: list[str][source]
routes_removed: list[str][source]
class pyfsr.models.VersionStepDelta(*, step: str | None = None, field: str | None = None, from_value: Any | None = None, to: Any | None = None, **extra_data: Any)[source]

Bases: ApiResult

One changed step between two playbook versions (diff_versions()).

field is the top-level step key that differs (arguments, name, stepType…); from / to are the old / new values (Any – may be dicts, strings, or None).

field: str | None[source]
from_value: Any | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

step: str | None[source]
to_value: Any | None[source]
class pyfsr.models.WaitProgress(*, tree: RunNode, poll_count: int, elapsed_s: float, is_terminal: bool)[source]

Bases: BaseModel

A single poll snapshot handed to the on_poll callback of wait_for_task().

Lets a caller act on the live run between polls – answer a pending manual-input gate, patch a field to unblock an SLA timer, log progress – without re-implementing the poll loop. tree is the freshly-fetched RunNode; poll_count counts polls so far (1-based); elapsed_s is seconds since the wait began; is_terminal is True on the final poll (the run has reached a terminal status and the wait is about to return).

Return False from the callback to stop waiting early and return the current tree; return None/True (or nothing) to keep polling.

elapsed_s: float[source]
is_terminal: bool[source]
model_config = {'arbitrary_types_allowed': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

poll_count: int[source]
tree: RunNode[source]

Module administration

class pyfsr.models.AttributeBulkAction(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, allow: bool = False, buttonText: str = '', buttonIcon: str = '', buttonClass: str = '', **extra_data: Any)[source]

Bases: BaseRecord

bulkAction sub-object on an attribute.

allow: bool[source]
button_class: str[source]
button_icon: str[source]
button_text: str[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.AttributeMetadata(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, type: str | None = None, formType: str | None = None, length: int | None = None, orderIndex: int | None = None, collection: bool = False, system: bool = False, encrypted: bool = False, searchable: bool = False, peerReplicable: bool = True, gridColumn: bool = False, skipSerialization: bool = False, htmlEscape: bool = False, visibility: bool | dict[str, ~typing.Any]=True, readable: bool = True, writeable: bool = True, unique: bool = False, recommend: bool = False, identifier: bool | None = None, orphanRemoval: bool | None = None, ownsRelationship: bool | None = None, inversedField: str | None = None, dataSource: dict[str, ~typing.Any] | list | None=None, dataSourceFilters: dict[str, ~typing.Any] | list | None=None, validation: AttributeValidation | dict[str, ~typing.Any] | None=None, bulkAction: AttributeBulkAction | dict[str, ~typing.Any] | None=None, defaultValue: Any = None, tooltip: str | None = None, displayName: str | None = None, descriptions: dict[str, str] | None=None, importedBy: list[Any] = <factory>, sattrib: RecordIRI | dict[str, ~typing.Any] | None=None, **extra_data: Any)[source]

Bases: BaseRecord

A single field definition on a staging or published module.

type is the Postgres storage type ("string", "integer", "boolean", "object", "picklists", or a module name like "alerts" for relationships). form_type is the display type (the field kind shown in the editor).

The sattrib field is the IRI of the parent StagingModelMetadata (or its dict form when the attribute is fetched via the staging module endpoint).

bulk_action: AttributeBulkAction | dict[str, Any] | None[source]
collection: bool[source]
data_source: dict[str, Any] | list | None[source]
data_source_filters: dict[str, Any] | list | None[source]
default_value: Any[source]
descriptions: dict[str, str] | None[source]
display_name: str | None[source]
encrypted: bool[source]
form_type: str | None[source]
grid_column: bool[source]
html_escape: bool[source]
id_iri: str | None[source]
identifier: bool | None[source]
imported_by: list[Any][source]
inverted_field: str | None[source]
property is_picklist: bool[source]

True when the field is a picklist or multiselect picklist.

property is_relationship: bool[source]

True when the field is a relationship (lookup / manyToMany / oneToMany).

property label: str | None[source]

Human-readable label from descriptions.singular or displayName.

length: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
order_index: int | None[source]
orphan_removal: bool | None[source]
owns_relationship: bool | None[source]
peer_replicable: bool[source]
readable: bool[source]
recommend: bool[source]
record_type: str | None[source]
sattrib: RecordIRI | dict[str, Any] | None[source]
searchable: bool[source]
skip_serialization: bool[source]
system: bool[source]
tooltip: str | None[source]
type: str | None[source]
unique: bool[source]
uuid: str | None[source]
validation: AttributeValidation | dict[str, Any] | None[source]
visibility: bool | dict[str, Any][source]
writeable: bool[source]
class pyfsr.models.AttributeValidation(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, required: bool = False, minlength: int | None = None, maxlength: int | None = None, _enableRange: bool | None = None, **extra_data: Any)[source]

Bases: BaseRecord

validation sub-object on an attribute.

enable_range: bool | None[source]
maxlength: int | None[source]
minlength: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

required: bool[source]
class pyfsr.models.DefaultSortEntry(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, field: str | None = None, direction: str | None = None, **extra_data: Any)[source]

Bases: BaseRecord

One entry in defaultSort on a module metadata record.

direction: str | None[source]
field: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.InvalidDraft(*, module: str | None = None, uuid: str | None = None, field: str | None = None, problem: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

One staged module/field whose name would break the next publish.

Synthesized by find_invalid_drafts(). field is set only for an attribute-level problem. Dict-compatible.

field: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
problem: str | None[source]
uuid: str | None[source]
class pyfsr.models.ModuleDescriptions(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, singular: str | None = None, plural: str | None = None, **extra_data: Any)[source]

Bases: BaseRecord

descriptions sub-object on a module metadata record.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

plural: str | None[source]
singular: str | None[source]
class pyfsr.models.ModuleMetadata(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, type: str | None = None, module: str | None = None, tableName: str | None = None, parentType: str | None = None, ownable: bool = False, userOwnable: bool = False, queueable: bool = False, trackable: bool = False, taggable: bool = False, peerReplicable: bool = True, indexable: bool = True, writable: bool = True, system: bool = False, softDeleteable: bool = False, archivable: bool = False, paused: bool = False, enableReplication: bool = True, partitionBy: str | None = None, archivalCriteria: dict[str, ~typing.Any] | None=None, archivalFilters: list[Any] = <factory>, replicationFilters: list[Any] = <factory>, defaultSort: list[~pyfsr.models._modules_admin.DefaultSortEntry | dict[str, ~typing.Any]]=<factory>, uniqueConstraint: list[dict[str, ~typing.Any]]=<factory>, displayName: str | None = None, descriptions: ModuleDescriptions | dict[str, str] | None=None, attributes: list[~pyfsr.models._modules_admin.AttributeMetadata | dict[str, ~typing.Any]]=<factory>, importedBy: list[Any] = <factory>, **extra_data: Any)[source]

Bases: BaseRecord

Shared shape for both staging (StagingModelMetadata) and published (ModelMetadata) module records.

attributes is only populated when the record is fetched individually (GET /api/3/staging_model_metadatas/{uuid}), not in list responses.

archivable: bool[source]
archival_criteria: dict[str, Any] | None[source]
archival_filters: list[Any][source]
attributes: list[AttributeMetadata | dict[str, Any]][source]
default_sort: list[DefaultSortEntry | dict[str, Any]][source]
descriptions: ModuleDescriptions | dict[str, str] | None[source]
display_name: str | None[source]
enable_replication: bool[source]
get_attribute(name: str) → AttributeMetadata | dict[str, Any] | None[source]

Return the attribute with name, or None.

id_iri: str | None[source]
imported_by: list[Any][source]
indexable: bool[source]
property label: str | None[source]

Human-readable singular label from descriptions.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
ownable: bool[source]
parent_type: str | None[source]
partition_by: str | None[source]
paused: bool[source]
peer_replicable: bool[source]
property plural_label: str | None[source]

Human-readable plural label from descriptions.

queueable: bool[source]
record_type: str | None[source]
replication_filters: list[Any][source]
soft_deleteable: bool[source]
system: bool[source]
table_name: str | None[source]
taggable: bool[source]
trackable: bool[source]
type: str | None[source]
unique_constraint: list[dict[str, Any]][source]
user_ownable: bool[source]
uuid: str | None[source]
writable: bool[source]
class pyfsr.models.PendingChange(*, module: str | None = None, change: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

One module with an uncommitted (staged-but-unpublished) schema change.

Synthesized by pending_changes() by diffing the staging vs published metadata stores. Dict-compatible (row["module"]).

change: str | None[source]

"created" (staging only), "deleted" (published only), or "modified".

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
class pyfsr.models.PublishedModelMetadata(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, type: str | None = None, module: str | None = None, tableName: str | None = None, parentType: str | None = None, ownable: bool = False, userOwnable: bool = False, queueable: bool = False, trackable: bool = False, taggable: bool = False, peerReplicable: bool = True, indexable: bool = True, writable: bool = True, system: bool = False, softDeleteable: bool = False, archivable: bool = False, paused: bool = False, enableReplication: bool = True, partitionBy: str | None = None, archivalCriteria: dict[str, ~typing.Any] | None=None, archivalFilters: list[Any] = <factory>, replicationFilters: list[Any] = <factory>, defaultSort: list[~pyfsr.models._modules_admin.DefaultSortEntry | dict[str, ~typing.Any]]=<factory>, uniqueConstraint: list[dict[str, ~typing.Any]]=<factory>, displayName: str | None = None, descriptions: ModuleDescriptions | dict[str, str] | None=None, attributes: list[~pyfsr.models._modules_admin.AttributeMetadata | dict[str, ~typing.Any]]=<factory>, importedBy: list[Any] = <factory>, **extra_data: Any)[source]

Bases: ModuleMetadata

A published module record from /api/3/model_metadatas.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.StagingModelMetadata(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, type: str | None = None, module: str | None = None, tableName: str | None = None, parentType: str | None = None, ownable: bool = False, userOwnable: bool = False, queueable: bool = False, trackable: bool = False, taggable: bool = False, peerReplicable: bool = True, indexable: bool = True, writable: bool = True, system: bool = False, softDeleteable: bool = False, archivable: bool = False, paused: bool = False, enableReplication: bool = True, partitionBy: str | None = None, archivalCriteria: dict[str, ~typing.Any] | None=None, archivalFilters: list[Any] = <factory>, replicationFilters: list[Any] = <factory>, defaultSort: list[~pyfsr.models._modules_admin.DefaultSortEntry | dict[str, ~typing.Any]]=<factory>, uniqueConstraint: list[dict[str, ~typing.Any]]=<factory>, displayName: str | None = None, descriptions: ModuleDescriptions | dict[str, str] | None=None, attributes: list[~pyfsr.models._modules_admin.AttributeMetadata | dict[str, ~typing.Any]]=<factory>, importedBy: list[Any] = <factory>, **extra_data: Any)[source]

Bases: ModuleMetadata

A staging module record from /api/3/staging_model_metadatas.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Export / import

class pyfsr.models.ConnectorSelection(*, value: str, label: str | None = None, version: str | None = None, include: bool = True, rpm: bool = True, configurations: bool = True, configCount: int = 0, recordCount: int = 0, **extra_data: Any)[source]

Bases: _ExportEntry

A connector selection (options.connectors[]).

value (the cyops-connector-<name>-<version> string) is the only field the engine keys on; a bare name is ignored. configurations toggles whether the connector’s saved configs (secrets) ride along.

configCount: int[source]
configurations: bool[source]
include: bool[source]
label: str | None[source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

recordCount: int[source]
rpm: bool[source]
value: str[source]
version: str | None[source]
class pyfsr.models.ModuleSelection(*, value: str, includedAttributes: list[str] | None = None, **extra_data: Any)[source]

Bases: _ExportEntry

A module schema selection (options.modules[]).

value is the module api name (e.g. "alerts"). includedAttributes limits the exported fields; leave None (the default) to export the whole schema — the key is then omitted from the wire, which the export engine reads as “all attributes” (an explicit empty list, by contrast, exports none).

includedAttributes: list[str] | None[source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

value: str[source]
class pyfsr.models.PlaybookCollectionSelection(*, value: str, label: str | None = None, include: bool = True, recordCount: int = 0, includeVersions: bool = True, includeSchedules: bool = True, includeGlobalVariables: bool = True, **extra_data: Any)[source]

Bases: _ExportEntry

A playbook-collection selection (options.playbooks.collections[]).

value is the collection uuid. The include* flags mirror the wizard’s Playbooks-step toggles for pulling the collection’s dependent content.

include: bool[source]
includeGlobalVariables: bool[source]
includeSchedules: bool[source]
includeVersions: bool[source]
label: str | None[source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

recordCount: int[source]
value: str[source]
class pyfsr.models.RecordSet(*, type: str, query: dict[str, Any], label: str | None = None, include: bool = True, includeCorrelations: bool = False, **extra_data: Any)[source]

Bases: _ExportEntry

A filtered record-data export (options.recordSets[]).

query must carry a limit (the record-export trigger — absent means the engine emits no records). The rest of query is a standard pyfsr.query.Query.to_body() dict, so filtering works as elsewhere.

include: bool[source]
includeCorrelations: bool[source]
label: str | None[source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

query: dict[str, Any][source]
type: str[source]
class pyfsr.models.ViewTemplateSelection(*, uuid: str, module: str, viewOptions: str | None = None, filters: list[Any] = [], **extra_data: Any)[source]

Bases: _ExportEntry

A view-template selection (options.viewTemplates[]).

Not a bare id: the export engine embeds the resolved system_view_templates row for a module/layout, keyed by uuid and carrying module, viewOptions ("list"/"detail"/"form"), and any record filters. Resolved live from the module’s templates at create_template() time (live-verified 8.0.0).

filters: list[Any][source]
model_config = {'extra': 'allow'}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str[source]
uuid: str[source]
viewOptions: str | None[source]

System & platform

class pyfsr.models.AIAgent(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: ContentHubItem

A Content Hub AI agent (type == "ai_agent"). FortiSOAR 8.0.0+.

AI agents ship through the same Content Hub catalog as packs and connectors and are served by the same /api/query/solutionpacks endpoint (@type on the wire is SolutionPack) — only the type discriminator differs. name is the agent id (e.g. "conversation") and label its display name (e.g. "Chat Assistant"); either resolves an agent via get_installed_ai_agent().

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.AggregateRow(**extra_data: Any)[source]

Bases: ApiResult

One row of a server-side aggregation.

Returned by aggregate(). The keys are the aliases supplied to that call — group-by fields keep the field’s last path segment, metrics use their explicit alias, and count=True adds total — so the shape is entirely caller-defined and every key lives in extra. Dict-compatible (row["total"] works alongside value()); value() is just a typed accessor for one alias.

Example:

rows = client.records("workflows").aggregate(
    group_by="triggerStep.stepType.name", count=True)
rows[0]["name"], rows[0].value("total")
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

value(alias: str, default: Any = None) → Any[source]

The value stored under alias (a group-by segment or metric alias).

class pyfsr.models.ApiKey(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, title: str | None = None, userId: str | None = None, roles: list[str] | None = None, teams: list[str] | None = None, avatar: Any | None = None, recordTags: list[Any] | None = None, userType: Any | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An API-key binding record from /api/3/api_keys/.

This is the scope object that binds roles/teams to an API-key user (the user record carrying the key material, created via /api/auth/users — ApiKeyUser). It is also an actor: it’s the record_type == "ApiKey" subtype of the shared actors table, so a record created via an API key expands its createUser / modifyUser to this record (@type == "ApiKey", IRI /api/3/api_keys/<uuid> — live-verified on 8.0.0). @type on the wire is ApiKey; the module slug is api_keys. The key value itself is masked on every read here ��� the plaintext lives on the API-key user, recoverable only at create time (or via show_api_key when retrievable_mode was on).

avatar: Any | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
id: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
recordTags: list[Any] | None[source]
roles: list[str] | None[source]
teams: list[str] | None[source]
title: str | None[source]
userId: str | None[source]
userType: Any | None[source]
class pyfsr.models.ApiKeyMaterial(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, key: str | None = None, retrievable: bool | None = None, status: str | None = None, valid_until: int | None = None, time_remaining: int | None = None, modify_date: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

The nested api_key block on an ApiKeyUser.

Carries the key value (masked unless read with show_api_key under retrievable_mode) and its validity/status metadata. Modeled as a BaseRecord so ak.get("key") / ak.get("retrievable") work — the plaintext-recovery helper in pyfsr.api.api_keys relies on that.

key: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modify_date: int | None[source]
retrievable: bool | None[source]
status: str | None[source]
time_remaining: int | None[source]
valid_until: int | None[source]
class pyfsr.models.ApiKeyUser(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, user_type: int | None = None, status: int | None = None, access_type: str | None = None, loginid: str | None = None, api_key: ApiKeyMaterial | None = None, bind_name: str | None = None, domain: str | None = None, is_logged_in: bool | None = None, tenant: Any | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An API-key user from /api/auth/users (usersresp[0]).

The user record that carries key material (user_type == 9), linked to the ApiKey binding — which is the actual actor-table row (record_type == "ApiKey") that shows up on createUser/modifyUser. Distinct from a People User. This /api/auth/users shape is not a JSON-LD /api/3 collection (no @id/@type on the wire), but BaseRecord works fine: id_iri/record_type stay None and dict-access (u["uuid"], u.get("api_key")) keeps working. The nested api_key is parsed into ApiKeyMaterial.

access_type: str | None[source]
api_key: ApiKeyMaterial | None[source]
bind_name: str | None[source]
domain: str | None[source]
is_logged_in: bool | None[source]
loginid: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

status: int | None[source]
tenant: Any | None[source]
user_type: int | None[source]
uuid: str | None[source]
class pyfsr.models.Appliance(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, title: str | None = None, userType: Any | None = None, avatar: Any | None = None, userId: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR appliance actor (@type == "Appliance").

One of the concrete subtypes of an actor: FortiSOAR stores all security principals in a single actors table using single-table inheritance keyed on the record_type discriminator (root-verified against the appliance’s Doctrine entities — Person extends Actor, same actors table). An Appliance is the record_type == "Appliance" sibling of a human User (record_type == "Person"); it appears as createUser / modifyUser on records created by the playbook engine itself, where name is typically "Playbook". The /api/3/appliances/ collection is the filtered view of these rows.

avatar: Any | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
id: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
title: str | None[source]
userId: str | None[source]
userType: Any | None[source]
class pyfsr.models.Attachment(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, file: FileRecord | str | None = None, type: str | None = None, assignee: User | str | None = None, recordTags: list[str] | None = None, createUser: str | User | None = None, createDate: float | None = None, modifyUser: str | User | None = None, modifyDate: float | None = None, id: int | str | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An /api/3/attachments record linking an uploaded FileRecord.

Field set captured from a live 7.6.5 /api/3/attachments response. file is the linked FileRecord (the create response expands it; a bare IRI string is also accepted). Storage/audit/tenancy keys stay in extra.

assignee: User | str | None[source]
createDate: float | None[source]
createUser: str | User | None[source]
description: str | None[source]
file: FileRecord | str | None[source]
id: int | str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | User | None[source]
name: str | None[source]
recordTags: list[str] | None[source]
type: str | None[source]
class pyfsr.models.ConnectorOperation(*, operation: str | None = None, title: str | None = None, description: str | None = None, visible: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

One action a connector exposes, from its info.json operations[].

Live-verified stable fields: the operation slug, human title / description, and the visible flag. Operation-specific extras (parameters, output schema, category, …) stay in extra. Dict-compatible.

description: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operation: str | None[source]
title: str | None[source]
visible: bool | None[source]
class pyfsr.models.ConnectorVersionInfo(*, name: str | None = None, label: str | None = None, description: str | None = None, version: str | None = None, type: str | None = None, buildNumber: int | None = None, publishedDate: int | None = None, lastUpdated: int | None = None, publisher: str | None = None, certified: bool | None = None, category: str | None = None, infoPath: str | None = None, help: str | None = None, releaseNotes: str | None = None, availableVersions: list[str] | None = None, **extra_data: Any)[source]

Bases: ApiResult

A connector’s published info.json from Fortinet’s public Content Hub repo.

Returned by connector_versions(). This is the repo manifest ({repo}/.../latest/info.json), a different shape from the on-box ContentHubConnector catalog entry — most notably it carries availableVersions, every version ever published. Curated fields are typed; the rest (scm, help, icon paths, …) stay in extra. Dict-compatible, so info["availableVersions"] still works.

availableVersions: list[str] | None[source]
buildNumber: int | None[source]
category: str | None[source]
certified: bool | None[source]
description: str | None[source]
help: str | None[source]
infoPath: str | None[source]
label: str | None[source]
lastUpdated: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
publishedDate: int | None[source]
publisher: str | None[source]
releaseNotes: str | None[source]
type: str | None[source]
version: str | None[source]
class pyfsr.models.ContentHubConnector(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: ContentHubItem

A Content Hub connector listing (type == "connector").

Named ContentHubConnector to avoid clashing with the live client.connectors (execution) surface — this is the catalog entry.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.ContentHubItem(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

Shared base for Content Hub items (solution packs, connectors, widgets).

Returned by client.content_hub searches. Stable, platform-owned schema (the marketplace catalog shape). Subclassed by SolutionPack, ContentHubConnector, and Widget, which add nothing of their own today — the catalog returns one flat shape discriminated by type — but exist so callers can isinstance-narrow and so future per-type fields have a home.

buildNumber: int | None[source]
category: list[Any] | None[source]
certified: bool | None[source]
configCount: int | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
dependencies: list[Any] | None[source]
description: str | None[source]
development: bool | None[source]
draft: bool | None[source]
featured: bool | None[source]
featuredTags: list[FeaturedTag] | None[source]
fsrMinCompatibility: str | None[source]
iconLarge: str | None[source]
importedBy: list[Any] | None[source]
infoPath: str | None[source]
installed: bool | None[source]
label: str | None[source]
latestAvailableVersion: str | None[source]
latestCompatibleVersion: str | None[source]
local: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
publishedDate: float | None[source]
publisher: str | None[source]
recordTags: Any | None[source]
status: str | None[source]
type: str | None[source]
version: str | None[source]
class pyfsr.models.DailyActionCount(*, daily_action_limit: int | None = None, remaining_actions: int | None = None, reset_time: int | None = None, last_update_time: float | None = None, **extra_data: Any)[source]

Bases: ApiResult

Daily action-count license usage — client.system.daily_action_count().

From GET /api/wf/workflow/config/?section=license (the endpoint the UI’s getDailyActionCount calls). Counters are decrypted by the workflow engine.

daily_action_limit is the per-day cap enforced by the license (e.g. 10000 on FortiFlex Starter); -1 means unlimited/unenforced (e.g. an Evaluation or edition with no action cap). remaining_actions counts down as counted steps run (Create/Update Record, Connector Action, Set Variable, …; Wait, Approval, Loops, and Reference-a-Playbook are not counted). reset_time is the epoch second at which remaining_actions resets to the limit.

daily_action_limit: int | None[source]
property enforced: bool[source]

True when a positive daily cap is in force (daily_action_limit > 0); -1/0 mean unlimited or unenforced.

last_update_time: float | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

remaining_actions: int | None[source]
reset_time: int | None[source]
property used_today: int | None[source]

Actions consumed so far today (daily_action_limit - remaining_actions), or None when not enforced.

class pyfsr.models.EmailTemplate(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, subject: str | None = None, content: str | None = None, visible: bool | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR email template record from /api/3/email_templates/.

Reusable subject/body used by notification playbooks and the SMTP connector’s “Email Template” body type. The module slug is email_templates; @type on the wire is EmailTemplate. subject and content may contain Jinja that the platform expands at send time (verified against a live 8.0 box).

content: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
subject: str | None[source]
visible: bool | None[source]
class pyfsr.models.ExportConnectorRef(*, name: str | None = None, value: str | None = None, version: str | None = None, rpm: bool | None = None, rpm_name: str | None = None, rpmExists: bool | None = None, exists: bool | None = None, include: bool | None = None, includeInstall: bool | None = None, install_mode: str | None = None, installer_path: str | None = None, configurations: bool | None = None, configCount: int | None = None, configurationCount: int | None = None, **extra_data: Any)[source]

Bases: ApiResult

One connector entry in an export template’s options.connectors.

Field set captured from a live 7.6.5 export-template options.connectors[] entry — every value is a scalar (str/bool/int).

config_count: int | None[source]
configuration_count: int | None[source]
configurations: bool | None[source]
exists: bool | None[source]
include: bool | None[source]
include_install: bool | None[source]
install_mode: str | None[source]
installer_path: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
rpm: bool | None[source]
rpm_exists: bool | None[source]
rpm_name: str | None[source]
value: str | None[source]
version: str | None[source]
class pyfsr.models.ExportOptions(*, connectors: list[ExportConnectorRef] = [], **extra_data: Any)[source]

Bases: ApiResult

An export template’s selection manifest (export_template.options).

connectors is modeled (see ExportConnectorRef). The manifest’s other selection lists are preserved verbatim in extra rather than typed, because their element shapes have not been captured populated from live wire — they are added here as they are observed, never guessed.

Live-verified on 8.0.0, an options manifest carries up to 25 category keys: actors, ai_agents, appSettings, connectors, dashboards, exportTemplates, externalTemplates, fixtures, mcp_configurations, modules, picklistNames, playbookBlocks, playbooks, postInstall, preInstall, preprocessingRules, recordSets, reports, roles, ruleChannels, rules, teams, viewTemplates, views, widgets.

connectors: list[ExportConnectorRef][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.ExportTemplate(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, options: ExportOptions | None = None, lastExportDate: float | None = None, type: str | None = None, createUser: str | User | None = None, createDate: float | None = None, modifyUser: str | User | None = None, modifyDate: float | None = None, id: int | str | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An /api/3/export_templates record — a reusable export selection.

Field set captured from a live /api/3/export_templates response. options is the typed ExportOptions selection manifest. type distinguishes the export kind — live-verified on 8.0.0 the values are "Export Wizard" (a normal config export) and "SolutionPack Export". Export bookkeeping (metadata, solutionPack) rides the wire but stays in extra until its element shape is captured populated.

createDate: float | None[source]
createUser: str | User | None[source]
id: int | str | None[source]
last_export_date: float | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | User | None[source]
name: str | None[source]
options: ExportOptions | None[source]
type: str | None[source]
class pyfsr.models.FeaturedTag(*, tag: str | None = None, color: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A marketplace “featured” badge on a Content Hub item.

The featuredTags array on a ContentHubItem carries these — live-verified shape is {"tag": "preview", "color": "#2d87e3"} (the label and the hex colour the catalog UI renders the chip with). Dict-compatible, so tag["tag"] works alongside tag.tag.

color: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

tag: str | None[source]
class pyfsr.models.FileRecord(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, filename: str | None = None, mimeType: str | None = None, size: int | None = None, uploadDate: float | None = None, thumbnail: Any | None = None, assignee: str | None = None, file: Any | None = None, metadata: Any | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, id: int | str | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A /api/3/files record, returned by upload().

Stable platform schema. The @id IRI (rec.iri) is what attachment, import, and similar payloads reference as their file field. filename and mimeType are the most-used typed fields; the rest of the storage metadata (size, content path, thumbnails) stays in extra.

assignee: str | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
file: Any | None[source]
filename: str | None[source]
id: int | str | None[source]
metadata: Any | None[source]
mimeType: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
size: int | None[source]
thumbnail: Any | None[source]
uploadDate: float | None[source]

Upload timestamp (epoch). The /api/3/files listing carries this rather than create/modifyDate (live-verified 8.0.0).

class pyfsr.models.ImportJob(*, id_iri: str | None = None, uuid: str | None = None, status: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The async import job embedded in a solution-pack install response.

POST /api/3/solutionpacks/install returns the pack entity with this object tracking the install; its uuid is what install_status() and wait_for_install() poll. Dict-compatible, so job["uuid"] works alongside job.uuid.

id_iri: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

status: str | None[source]
uuid: str | None[source]
class pyfsr.models.ManualInput(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, id: int | None = None, record: str | None = None, type: str | None = None, title: str | None = None, external_channel_list: list[Any] | None = None, inline_channel_list: list[Any] | None = None, owners: list[Any] | None = None, assignment_type: str | None = None, owner_details: dict[str, Any] | None = None, created: str | None = None, timeout: Any | None = None, timeout_details: Any | None = None, step_id: int | None = None, unauthenticated_input: bool | None = None, agent_id: str | None = None, is_approval: bool | None = None, workflow: str | int | None = None, input: ManualInputForm | None = None, response_mapping: ResponseMapping | None = None, custom_fields: dict[str, Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A pending manual workflow input from /api/wf/api/manual-wf-input/.

A playbook paused on a Manual Input / Approval step, waiting on a human. This is a wf API entity, not a /api/3 module, so there is no JSON-LD envelope – id (int) is the identity and id_iri/record_type stay None. Field set captured from a live 8.0 box.

workflow is the encrypted run token (Fernet), step_id the paused step, and is_approval distinguishes an approval gate from a data-input prompt. assignment_type / owners / owner_details describe who the input is assigned to.

title is the prompt’s schema title – the Manual Input step’s title:, mirrored from input.schema.title – not the step name. They coincide only when the step declares no title:, in which case the schema title defaults to the step name.

agent_id: str | None[source]
assignment_type: str | None[source]
created: str | None[source]
custom_fields: dict[str, Any] | None[source]
external_channel_list: list[Any] | None[source]
id: int | None[source]
inline_channel_list: list[Any] | None[source]
input: ManualInputForm | None[source]
is_approval: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

owner_details: dict[str, Any] | None[source]
owners: list[Any] | None[source]
record: str | None[source]
response_mapping: ResponseMapping | None[source]
step_id: int | None[source]
timeout: Any | None[source]
timeout_details: Any | None[source]
title: str | None[source]
type: str | None[source]
unauthenticated_input: bool | None[source]
workflow: str | int | None[source]
class pyfsr.models.ManualInputForm(*, schema: ManualInputSchema | None = None, **extra_data: Any)[source]

Bases: ApiResult

The input object of a retrieved Manual Input: wraps the form schema.

The wire key is schema; it is exposed as the schema_ attribute (schema shadows BaseModel.schema) but stays reachable by its wire name through dict access – form["schema"] returns the typed ManualInputSchema.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

schema_: ManualInputSchema | None[source]
class pyfsr.models.ManualInputOption(*, option: str | None = None, step_iri: str | None = None, primary: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

One response button of a Manual Input (response_mapping.options[]).

option is the button label; step_iri the workflow step the run routes to when chosen (an /api/3/workflow_steps/<uuid> IRI); primary marks the default/highlighted button (absent on plain buttons). Live-verified from retrieve_wfinput.

step_iri is wired at author time from the step’s next:, so a Manual Input step with no next step yields an option without one. Such a run cannot be resumed – wfinput_resume 500s on a null or absent step_iri – which answer() reports up front.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

option: str | None[source]
primary: bool | None[source]
step_iri: str | None[source]
class pyfsr.models.ManualInputResume(*, task_id: str | None = None, message: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The ack from resuming a manual input (.../wfinput_resume/).

Live-verified shape: task_id (the async resume task) plus the step’s message (e.g. "Awaiting Playbook resumed successfully."). Dict-compatible, so resp["task_id"] works alongside resp.task_id.

message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

task_id: str | None[source]
class pyfsr.models.ManualInputSchema(*, title: str | None = None, description: str | None = None, inputVariables: list[ManualInputVariable] | None = None, **extra_data: Any)[source]

Bases: ApiResult

The form schema of a Manual Input prompt (input.schema).

Live-verified: title / description are the prompt header, and inputVariables the ordered list of fields the user fills in (empty for a button-only / DecisionBased prompt).

description: str | None[source]
inputVariables: list[ManualInputVariable] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

title: str | None[source]
class pyfsr.models.ManualInputVariable(*, name: str | None = None, type: str | None = None, label: str | None = None, title: str | None = None, tooltip: str | None = None, dataType: str | None = None, formType: str | None = None, required: bool | None = None, options: list[Any] | None = None, defaultValue: Any | None = None, templateUrl: str | None = None, playbookField: bool | None = None, jinjaExpressionView: bool | None = None, useRecordFieldDefault: bool | None = None, usable: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

One field in a Manual Input prompt’s collected form (inputVariables[]).

Field set captured from a live retrieve_wfinput response: a friendly inputs: field compiles to this canonical shape. name is the variable referenced after resume as vars.steps.<step>.input.<name>; formType / dataType / type / templateUrl drive how FortiSOAR renders and validates the widget (e.g. formType="dynamicList" with options is a select; required gates submission). options is present only for the list widgets. Unknown/internal keys (_expanded, _previousName, …) ride through via extra="allow".

dataType: str | None[source]
defaultValue: Any | None[source]
formType: str | None[source]
jinjaExpressionView: bool | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
options: list[Any] | None[source]
playbookField: bool | None[source]
required: bool | None[source]
templateUrl: str | None[source]
title: str | None[source]
tooltip: str | None[source]
type: str | None[source]
usable: bool | None[source]
useRecordFieldDefault: bool | None[source]
class pyfsr.models.ModulePermission(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, canCreate: bool | None = None, canRead: bool | None = None, canUpdate: bool | None = None, canDelete: bool | None = None, canExecute: bool | None = None, fieldPermissions: list[Any] | None = None, module: Any | None = None, **extra_data: Any)[source]

Bases: BaseRecord

One module’s CRUD/execute grant inside a Role.

Live-verified shape (@type == "ModulePermission"): the five can* booleans, an optional fieldPermissions list, and a module relationship (an IRI string, or the expanded module object when relationships are pulled). Dict-compatible, so perm["canRead"] works alongside perm.canRead.

canCreate: bool | None[source]
canDelete: bool | None[source]
canExecute: bool | None[source]
canRead: bool | None[source]
canUpdate: bool | None[source]
fieldPermissions: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: Any | None[source]
class pyfsr.models.NavigationView(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, type: str | None = None, module: str | None = None, owner: str | None = None, config: dict[str, Any] | None = None, filters: list[Any] | None = None, viewOptions: str | None = None, isDefault: bool | None = None, system: bool | None = None, visible: bool | None = None, **extra_data: Any)[source]

Bases: BaseRecord

The “app” navigation view (GET /api/views/1/app).

A single view record (not a collection) describing the left-hand navigation. config["navigation"] is the list of top-level sections, each a dict with a title and optional nested items — those titles are what the views export category ships. Use navigation_sections() to read them without walking config by hand.

Note this endpoint returns the view without a JSON-LD envelope, so iri is None; uuid identifies it.

config: dict[str, Any] | None[source]
filters: list[Any] | None[source]
isDefault: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
name: str | None[source]
owner: str | None[source]
property section_titles: list[str][source]

Titles of the top-level navigation sections, in display order.

property sections: list[dict[str, Any]][source]

Top-level navigation sections from config["navigation"] ([] if absent).

system: bool | None[source]
type: str | None[source]
uuid: str | None[source]
viewOptions: str | None[source]
visible: bool | None[source]
class pyfsr.models.Notification(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, content: str | None = None, footer: list[Any] | None = None, entity_type: str | None = None, event_type: str | None = None, entity_id: str | None = None, read: bool | None = None, dismissible: bool | None = None, created_on: str | None = None, roles: list[Any] | None = None, user: str | None = None, teams: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR system notification from /api/rule/api/system-notification/notifications/.

The per-user bell-icon notifications the platform raises for record events (task assignments, approvals, SLA breaches, …). This is a rule API entity, not a /api/3 module, so there is no JSON-LD envelope — uuid is the identity and id_iri/record_type stay None. The listing is fetched with POST (see NotificationsAPI), not GET. Field set re-verified against a live 8.0.0 box (no drift).

content is the rendered HTML shown in the notification panel; entity_type / entity_id point at the record the event fired on (e.g. "tasks" plus a uuid), and event_type is the action ("create" / "update" / …). read / dismissible drive the panel’s unread badge and dismiss control.

content: str | None[source]
created_on: str | None[source]
dismissible: bool | None[source]
entity_id: str | None[source]
entity_type: str | None[source]
event_type: str | None[source]
footer: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

read: bool | None[source]
roles: list[Any] | None[source]
teams: list[Any] | None[source]
user: str | None[source]
class pyfsr.models.NotificationPurge(*, result: str | None = None, status: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The ack from a system-notification purge (.../system-notification/purge/).

Live-verified shape: result (human message) and status (e.g. "started" – the purge runs asynchronously). Dict-compatible.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

result: str | None[source]
status: str | None[source]
class pyfsr.models.PicklistItem(*, id_iri: str | None = None, uuid: str | None = None, itemValue: str | None = None, listName: str | dict[str, Any] | None = None, orderIndex: int | None = None, color: str | None = None, icon: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

One item (option) of a picklist, from GET /api/3/picklists or a create.

The bulk listing returns every item across every picklist in one page; each carries its own @id (the IRI the API stores on records), its friendly itemValue, and the listName IRI of the picklist it belongs to. Map that listName IRI to a name via GET /api/3/picklist_names. Curated fields are typed (itemValue/order_index/color/icon); the rest of the JSON-LD envelope rides through in extra. Dict-compatible.

order_index is the wire orderIndex (the int sort key). The legacy ordinal attribute is kept as a read alias so existing callers keep working.

color: str | None[source]
icon: str | None[source]
id_iri: str | None[source]
property iri: str | None[source]

The IRI a record stores for this item (/api/3/picklists/<uuid>).

itemValue: str | None[source]
listName: str | dict[str, Any] | None[source]
property list_name_iri: str | None[source]

The owning picklist’s listName IRI, whether it arrived as a string or an expanded {@id: ...} dict.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

order_index: int | None[source]
property ordinal: int | None[source]

Legacy alias for order_index (the wire field is orderIndex).

uuid: str | None[source]
class pyfsr.models.PicklistName(*, id_iri: str | None = None, uuid: str | None = None, name: str | None = None, system: bool | None = None, picklists: list[PicklistItem] | None = None, **extra_data: Any)[source]

Bases: ApiResult

A picklist list (the taxonomy an option belongs to), from GET /api/3/picklist_names or a create.

Each list carries a friendly name (unique instance-wide — a duplicate POST 409s with UniqueConstraintViolationException), a system flag, and its picklists items (embedded only when the request asks for $relationships=true; absent/empty otherwise). iri is the /api/3/picklist_names/<uuid> an option’s listName points back at. Dict-compatible; the JSON-LD envelope (@context/@type/id/ importedBy) rides through in extra.

id_iri: str | None[source]
property iri: str | None[source]

The list’s IRI (/api/3/picklist_names/<uuid>) — what an option’s listName field references.

property items: list[PicklistItem][source]

The list’s options (embedded under $relationships=true); empty when not expanded or the list has none.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
picklists: list[PicklistItem] | None[source]
system: bool | None[source]
uuid: str | None[source]
class pyfsr.models.PostInstallConfig(*, enabled: bool | None = None, widgets: list[PostInstallWidget] | None = None, **extra_data: Any)[source]

Bases: ApiResult

A solution pack’s post-install action (infoContent.postInstallConfig).

Live shape {"enabled": true, "widgets": [{...}]} — enabled mirrors the wizard’s Configure post-install action checkbox and widgets holds the widget(s) to offer after install (the wizard authors exactly one). There is no matching pre-install action in the wizard; the preInstall/postInstall export-manifest keys are unrelated install-time scripts. Dict-compatible.

enabled: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

widgets: list[PostInstallWidget] | None[source]
class pyfsr.models.PostInstallWidget(*, name: str | None = None, label: str | None = None, version: str | None = None, buttonLabel: str | None = None, autoLaunch: bool | None = None, autoLaunchTriggered: bool | None = None, **extra_data: Any)[source]

Bases: ApiResult

One widget in a solution pack’s post-install action.

A pack’s Configure post-install action offers a widget the operator can launch once the pack is installed. Shape captured from a live pack’s info.json and confirmed against the 8.0.0 editor’s solution-pack metadata wizard: the dropdown sets name/label/version together, and the two controls beside it set buttonLabel (the launch button’s text, required when the action is enabled) and autoLaunch (“Launch automatically the first time”). autoLaunchTriggered is runtime-only — the install flow sets it after the first auto-launch — and is never authored. Dict-compatible.

autoLaunch: bool | None[source]
autoLaunchTriggered: bool | None[source]
buttonLabel: str | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
version: str | None[source]
class pyfsr.models.QueryDefinition(*, logic: str = 'AND', filters: list[QueryFilter] = <factory>, limit: int | None = None, page: int | None = None, search: str | None = None, sort: list[Any] | None = None, aggregates: list[Any] | None = None, **extra_data: Any)[source]

Bases: ApiResult

The query body of a SystemQuery (also the shape you POST to /api/query/<module>).

Warning

logic is load-bearing. Omit it and FortiSOAR drops every filter on the floor and returns the whole module — no error, no warning. Same for a filter missing QueryFilter.type. Live-verified on 8.0.0:

{"filters": [{"field": "source", "operator": "eq", "value": "nope"}]}
    -> ALL records
{"logic": "AND", "filters": [
    {"field": "source", "operator": "eq", "value": "nope",
     "type": "primitive"}]}
    -> 0 records

Never delete straight from a query result without re-checking each record client-side.

aggregates: list[Any] | None[source]
filters: list[QueryFilter][source]
limit: int | None[source]
logic: str[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

page: int | None[source]
search: str | None[source]
sort: list[Any] | None[source]
class pyfsr.models.QueryFilter(*, field: str | None = None, operator: str | None = None, value: Any | None = None, type: str | None = None, logic: str | None = None, filters: list[QueryFilter] | None = None, **extra_data: Any)[source]

Bases: ApiResult

One condition inside a SystemQuery’s query.filters.

type is not cosmetic and must not be dropped: FortiSOAR silently ignores a filter that omits it (and silently ignores every filter when the enclosing body omits logic), returning all records rather than an error — see QueryDefinition. Use primitive for scalars, object for picklist/IRI values, datetime for dates.

A nested group sets logic + filters instead of field/value.

field: str | None[source]
filters: list[QueryFilter] | None[source]
logic: str | None[source]

Nested group members (a group carries logic + filters).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operator: str | None[source]
type: str | None[source]
value: Any | None[source]
class pyfsr.models.RepoConnectorEntry(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: str | list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, path: str | None = None, rpm_name: str | None = None, rpm_full_name: str | None = None, icon: str | None = None, **extra_data: Any)[source]

Bases: ContentHubItem

One entry from the public connectors.json manifest (repo.fortisoar.fortinet.com/connectors/info/connectors.json).

Returned by pyfsr.repo.list_connectors() / pyfsr.repo.search_connectors() — the no-appliance catalog, distinct from the on-box ContentHubConnector (which needs an appliance). The manifest is latest-version-only per connector and carries the RPM packaging fields the catalog entry doesn’t; those are typed here. The catalog-shaped fields (name/label/version/description/ category) come from ContentHubItem. Dict-compatible.

category: str | list[Any] | None[source]
property category_str: str | None[source]

category flattened to a string (", "-join of a list entry).

icon: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

path: str | None[source]
rpm_full_name: str | None[source]
rpm_name: str | None[source]
class pyfsr.models.Report(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, displayName: str | None = None, type: str | None = None, templateType: str | None = None, parentTemplateId: str | None = None, config: dict[str, Any] | None = None, filterArray: list[Any] | None = None, importedBy: list[Any] | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, updateDate: float | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A report (GET /api/3/reporting).

The report definitions behind the SOAR UI’s Reports section. Note the display name is displayName, not name — there is no name field on this entity, which is why report lookups match on displayName.

config holds the report layout/definition and filterArray its saved filters. templateType distinguishes shipped templates from user-authored reports; parentTemplateId links a report back to the template it was cloned from (None for originals).

config: dict[str, Any] | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
displayName: str | None[source]
filterArray: list[Any] | None[source]
importedBy: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
parentTemplateId: str | None[source]
templateType: str | None[source]
type: str | None[source]
updateDate: float | None[source]
class pyfsr.models.ResponseMapping(*, options: list[ManualInputOption] | None = None, duplicateOption: bool | None = None, customSuccessMessage: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A Manual Input’s response options + post-resume messaging (response_mapping).

Live-verified: options are the buttons, duplicateOption the allow-duplicate flag, customSuccessMessage the toast shown on resume.

customSuccessMessage: str | None[source]
duplicateOption: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

options: list[ManualInputOption] | None[source]
class pyfsr.models.ReusableBlock(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, type: str | None = None, reusable: bool | None = None, hasTriggerStep: bool | None = None, hideInLogs: bool | None = None, recordTags: list[str] | None = None, metadata: dict[str, Any] | list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A reusable playbook block — a workflow_groups row with reusable=true.

The saved, re-droppable step group surfaced in the playbook editor and the Configuration Export wizard’s Playbook Blocks category. From GET /api/3/workflow_groups?reusable=true (live-verified 8.0).

description: str | None[source]
hasTriggerStep: bool | None[source]
hideInLogs: bool | None[source]
metadata: dict[str, Any] | list[Any] | None[source]

Editor canvas metadata; a bare [] when unset (live-verified 8.0).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
recordTags: list[str] | None[source]
reusable: bool | None[source]
type: str | None[source]
class pyfsr.models.Role(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, modulePermissions: list[ModulePermission] | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR role record from /api/3/roles/.

A role bundles module permissions and is assigned to users. The module slug is roles; @type on the wire is Role. modulePermissions is only populated when the record is fetched with $relationships=true (verified against a live 7.6.5 box).

description: str | None[source]
importedBy: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modulePermissions: list[ModulePermission] | None[source]
name: str | None[source]
class pyfsr.models.SolutionPack(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: ContentHubItem

A Content Hub solution pack (type == "solutionpack").

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.SolutionPackInfo(*, name: str | None = None, label: str | None = None, version: str | None = None, description: str | None = None, availableVersions: list[str] | None = None, dependencies: list[Any] | None = None, fsrMinCompatibility: str | None = None, category: str | list[Any] | None = None, publisher: str | None = None, certified: bool | None = None, postInstallConfig: PostInstallConfig | None = None, **extra_data: Any)[source]

Bases: ApiResult

A solution-pack’s published info.json from Fortinet’s public content repo.

Returned by pyfsr.repo.solution_pack_info(). Carries availableVersions (full publish history) plus dependencies and fsrMinCompatibility. Note there is no public manifest for solution packs and slug resolution is unreliable, so discovery (name -> slug) still needs pyfsr.api.content_hub.ContentHubSearch.search_available_packs() on an appliance; this function is the per-version detail lookup once you know the slug. Curated fields are typed; the rest (contents, prerequisite, recordTags, featuredTags, …) stays in extra. Dict-compatible.

availableVersions: list[str] | None[source]
category: str | list[Any] | None[source]
certified: bool | None[source]
dependencies: list[Any] | None[source]
description: str | None[source]
fsrMinCompatibility: str | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
postInstallConfig: PostInstallConfig | None[source]
publisher: str | None[source]
version: str | None[source]
class pyfsr.models.SolutionPackInstallResponse(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, importJob: ImportJob | None = None, **extra_data: Any)[source]

Bases: SolutionPack

The SolutionPack record returned by POST /api/3/solutionpacks/install.

The install response is the full SolutionPack entity with an embedded ImportJob tracking the async install. Use job_id to get the UUID for install_status() and wait_for_install() calls.

importJob: ImportJob | None[source]
property job_id: str | None[source]

UUID of the async import job, parsed from the embedded ImportJob.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.SystemQuery(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, models: str | dict[str, Any] | None = None, query: QueryDefinition | None = None, assignee: str | None = None, advanced: Any | None = None, resultCacheSeconds: int | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A saved dataset from /api/3/system_queries/.

A system query is a named, module-scoped filter — what the UI calls a dataset. Beyond driving saved views, a dataset on threat_intel_feeds is a TAXII collection: the collection id served at /api/taxii/1/collections/<id>/objects is this record’s uuid (live-verified on 8.0.0). That is how FortiSOAR publishes an outgoing threat feed — see TaxiiAPI.

models is the target module’s model_metadatas IRI (expanded to the full object on read).

advanced: Any | None[source]
assignee: str | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

models: str | dict[str, Any] | None[source]
modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
property module: str | None[source]

The module slug this dataset targets (models.type when expanded).

name: str | None[source]
query: QueryDefinition | None[source]
resultCacheSeconds: int | None[source]
class pyfsr.models.SystemViewTemplate(*, id_iri: str | None = None, uuid: str | None = None, name: str | None = None, module: str | None = None, viewOptions: str | None = None, type: str | None = None, isDefault: bool | None = None, system: bool | None = None, visible: bool | None = None, config: dict[str, Any] | list[Any] | None = None, filters: list[Any] | None = None, **extra_data: Any)[source]

Bases: ApiResult

A system_view_templates row — a module/layout’s view configuration.

From GET /api/3/system_view_templates (bulk list) or GET /api/views/1/{name} (single named template), used by ViewTemplatesAPI. A “default” is not a separate resource — it’s this row’s isDefault flag, exactly one of which is True per (module, viewOptions) pair (verified live, 8.0).

Template names are not unique across layouts: a module ships one “Default Layout” row per viewOptions (list/detail/form), so resolving a template by name alone must also scope by viewOptions (see UserSettingsAPI.resolve_view_template, which learned this the hard way). config (the layout body — rows/columns/widgets) is typed loosely since its shape varies by type; the JSON-LD envelope (@context/@type) rides through extra. Dict-compatible.

config: dict[str, Any] | list[Any] | None[source]

The layout body (rows/columns/widgets); shape varies by type and is a bare [] for some modules’ empty layouts (live-verified 8.0).

filters: list[Any] | None[source]

Record filters scoping this view (the export wizard selects these); usually [] for a system default layout.

id_iri: str | None[source]
property iri: str | None[source]

The row’s IRI (/api/3/system_view_templates/<uuid>).

isDefault: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
name: str | None[source]
system: bool | None[source]

Platform-shipped flag (vs a user-authored template).

type: str | None[source]

Storage type, e.g. "rows" / "form" / "gridColumns" (None for some rows; live-verified 8.0).

uuid: str | None[source]
viewOptions: str | None[source]

Layout kind – "list", "detail", "form", or "settings" (live-verified 8.0).

visible: bool | None[source]

Whether the template is exposed in the layout picker.

class pyfsr.models.Team(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, importedBy: list[Any] | None = None, actors: list[User | Appliance | ApiKey | str] | None = None, parents: list[Any] | None = None, siblings: list[Any] | None = None, children: list[Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR team record from /api/3/teams/.

Teams own records (the owners relationship) and scope visibility. The module slug is teams; @type on the wire is Team. The schema is deliberately slim — verified against a live 7.6.5 box, a team record carries only name/description/importedBy beyond the JSON-LD/uuid envelope.

actors: list[Actor | str] | None[source]
children: list[Any] | None[source]
description: str | None[source]
importedBy: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
parents: list[Any] | None[source]
siblings: list[Any] | None[source]
class pyfsr.models.User(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, firstname: str | None = None, lastname: str | None = None, title: str | None = None, email: str | None = None, department: str | None = None, description: str | None = None, phoneWork: str | None = None, phoneMobile: str | None = None, phoneHome: str | None = None, phoneFax: str | None = None, csActive: bool | None = None, accessType: str | None = None, userType: Any | None = None, type: Any | None = None, avatar: Any | None = None, companyId: Any | None = None, userId: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A FortiSOAR user (Person) record from /api/3/people/.

A Person is the human subtype of an actor. FortiSOAR keeps every security principal in one actors table via single-table inheritance keyed on the record_type discriminator (root-verified: Person extends the base Actor entity, same actors table); the sibling subtypes are Appliance (record_type == "Appliance") and the ApiKey actor (record_type == "ApiKey"). @type on the wire is Person and the module slug is people — the /api/3/people collection is the person-only view of the shared table, whereas /api/3/actors spans all subtypes.

This is the entity behind every createUser / modifyUser / assignedTo relationship: when a record is pulled with relationships expanded those fields arrive as a full Person object, and BaseRecord.create_user() / modify_user() / assigned_to() parse them into this model (dispatching to Appliance when the expanded @type is Appliance).

accessType: str | None[source]
avatar: Any | None[source]
companyId: Any | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
csActive: bool | None[source]
department: str | None[source]
description: str | None[source]
email: str | None[source]
firstname: str | None[source]
id: int | None[source]
lastname: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
property name: str | None[source]

Display name ("firstname lastname"), or None if neither is set.

phoneFax: str | None[source]
phoneHome: str | None[source]
phoneMobile: str | None[source]
phoneWork: str | None[source]
title: str | None[source]
type: Any | None[source]
userId: str | None[source]
userType: Any | None[source]
class pyfsr.models.Widget(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, description: str | None = None, type: str | None = None, version: str | None = None, installed: bool | None = None, latestAvailableVersion: str | None = None, latestCompatibleVersion: str | None = None, fsrMinCompatibility: str | None = None, publisher: str | None = None, certified: bool | None = None, featured: bool | None = None, featuredTags: list[FeaturedTag] | None = None, draft: bool | None = None, local: bool | None = None, development: bool | None = None, dependencies: list[Any] | None = None, category: list[Any] | None = None, iconLarge: str | None = None, infoPath: str | None = None, publishedDate: float | None = None, buildNumber: int | None = None, configCount: int | None = None, status: str | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, recordTags: Any | None = None, importedBy: list[Any] | None = None, **extra_data: Any)[source]

Bases: ContentHubItem

A Content Hub widget (type == "widget").

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.WidgetInfo(*, name: str | None = None, title: str | None = None, subTitle: str | None = None, version: str | None = None, description: str | None = None, compatibility: list[str] | None = None, publisher: str | None = None, certified: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A widget’s published info.json from Fortinet’s public content repo.

Returned by pyfsr.repo.widget_info(). Different shape from the connector info.json ��� the widget payload nests human fields under a metadata wrapper (which rides through in extra) and carries a compatibility list instead of availableVersions (a widget info.json is per-version only; there is no public version-history manifest for widgets). Curated fields are typed; the rest stays in extra. Dict-compatible.

certified: str | None[source]
compatibility: list[str] | None[source]
description: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
publisher: str | None[source]
subTitle: str | None[source]
title: str | None[source]
version: str | None[source]
class pyfsr.models.Workflow(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, aliasName: str | None = None, tag: str | None = None, description: str | None = None, isActive: bool | None = None, debug: bool | None = None, singleRecordExecution: bool | None = None, remoteExecutableFlag: bool | None = None, synchronous: bool | None = None, triggerLimit: Any | None = None, parameters: list[str] | None = None, lastModifyDate: int | None = None, collection: str | None = None, triggerStep: str | None = None, priority: PicklistIRI | None = None, playbookOrigin: PicklistIRI | None = None, isEditable: bool | None = None, isPrivate: bool | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, deletedAt: float | None = None, importedBy: list[Any] | None = None, recordTags: list[str] | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A playbook (workflow) record from /api/3/workflows/.

Stable platform schema. collection is the IRI of the owning WorkflowCollection; triggerStep the IRI of the start step.

aliasName: str | None[source]
collection: str | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
debug: bool | None[source]
deletedAt: float | None[source]
description: str | None[source]
id: int | None[source]
importedBy: list[Any] | None[source]
isActive: bool | None[source]
isEditable: bool | None[source]
isPrivate: bool | None[source]
lastModifyDate: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
parameters: list[str] | None[source]
playbookOrigin: PicklistIRI | None[source]
priority: PicklistIRI | None[source]
recordTags: list[str] | None[source]
remoteExecutableFlag: bool | None[source]
singleRecordExecution: bool | None[source]
synchronous: bool | None[source]
tag: str | None[source]
triggerLimit: Any | None[source]
triggerStep: str | None[source]
class pyfsr.models.WorkflowCollection(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, visible: bool | None = None, image: Any | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, deletedAt: float | None = None, importedBy: list[Any] | None = None, recordTags: list[str] | None = None, workflows: list[Any] | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A playbook collection from /api/3/workflow_collections/.

The folder that groups playbooks; stable platform schema.

createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
deletedAt: float | None[source]
description: str | None[source]
id: int | None[source]
image: Any | None[source]
importedBy: list[Any] | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
recordTags: list[str] | None[source]
visible: bool | None[source]
workflows: list[Any] | None[source]
class pyfsr.models.WorkflowRun(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, status: str | None = None, created: str | None = None, modified: str | None = None, parent_wf: Any | None = None, tags: str | None = None, debug: bool | None = None, node_name: str | None = None, task_id: str | None = None, result: Any | None = None, template_iri: str | None = None, user: Any | None = None, steps: Any | None = None, env: Any | None = None, metadata: Any | None = None, peer_details: Any | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A playbook run record from /api/wf/api/(historical-)workflows/.

The raw run entity. PlaybooksAPI also exposes a flattened shape via its default (dict) return; pass typed=True there to get this model instead.

created: str | None[source]
debug: bool | None[source]
env: Any | None[source]
metadata: Any | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified: str | None[source]
name: str | None[source]
node_name: str | None[source]
parent_wf: Any | None[source]
peer_details: Any | None[source]
result: Any | None[source]
status: str | None[source]
steps: Any | None[source]
tags: str | None[source]
task_id: str | None[source]
template_iri: str | None[source]
user: Any | None[source]

AI & investigations

class pyfsr.models.AgentConfig(*, name: str | None = None, config_type: str | None = None, llm_provider: str | None = None, mcp_server: list[str] = <factory>, masking_agent: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

The inner config of an AgentConfigDTO.

mcp_server is the per-agent MCP-server allowlist (uuids); an agent left on the default config reports config_type == "default".

config_type: str | None[source]
llm_provider: str | None[source]
masking_agent: str | None[source]
mcp_server: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
class pyfsr.models.AgentConfigDTO(*, agent_name: str | None = None, agent_version: str | None = None, name: str | None = None, default: bool = False, config: AgentConfig = <factory>, config_id: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

AiAgentConfigurationDTO – response of the agent-config endpoints (GET/POST /api/ai/agent/config/{name}/{version} and .../default).

agent_name: str | None[source]
agent_version: str | None[source]
config: AgentConfig[source]
config_id: str | None[source]
default: bool[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
class pyfsr.models.AgentRecord(*, id: int | None = None, uuid: str | None = None, name: str | None = None, label: str | None = None, version: str | None = None, description: str | None = None, tags: list[str] = <factory>, category: str | None = None, active: bool | None = None, status: str | None = None, classpath: str | None = None, system: bool | None = None, installed: bool | None = None, inputformat: dict[str, ~typing.Any]=<factory>, outputformat: dict[str, ~typing.Any]=<factory>, config_schema: Any | None = None, configuration: list[Any] = <factory>, prompt: Any | None = None, additional_information: list[dict[str, ~typing.Any]]=<factory>, config_count: int | None = None, dependencies: list[Any] = <factory>, jailbreakguard: bool | None = None, llmconfig: Any | None = None, piimasking: bool | None = None, **extra_data: Any)[source]

Bases: _Lenient

One installed AI agent (GET /api/ai/agent/ / GET .../{name}/{version}).

active: bool | None[source]
additional_information: list[dict[str, Any]][source]
category: str | None[source]
classpath: str | None[source]
config_count: int | None[source]
config_schema: Any | None[source]
configuration: list[Any][source]
dependencies: list[Any][source]
description: str | None[source]
id: int | None[source]
inputformat: dict[str, Any][source]
installed: bool | None[source]
jailbreakguard: bool | None[source]
label: str | None[source]
llmconfig: Any | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
outputformat: dict[str, Any][source]
piimasking: bool | None[source]
prompt: Any | None[source]
status: str | None[source]
system: bool | None[source]
tags: list[str][source]
uuid: str | None[source]
version: str | None[source]
class pyfsr.models.AgentRun(*, trace_id: str, agent: str | None = None, status: str | None = None, question: str | None = None, answer: Any = None, evidence: Any = None, confidence: Any = None, tool_calls: list[TracedToolCall] = <factory>, llm_calls: int = 0, steps: list[str] = <factory>, errors: list[dict[str, ~typing.Any]]=<factory>, tokens: dict[str, ~typing.Any]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

One agent run (one sub-trace) of an investigation, as the Trace Flow shows it.

A provider agent run answers one investigation question: question comes from its root span input, answer/evidence/confidence from its root span output. Planner/hypothesis/summary runs have no question. steps is the ordered TOOL/LLM sequence, e.g. ["T:get_indicators", "LLM->get_alerts_linked_to_indicators", "LT:get_alerts_linked_to_indicators", "LLM"] (T = code-run tool, LT = LLM-chosen tool, LLM->x = an LLM step that asked for x). errors lists every non-plumbing span that ended in ERROR.

agent: str | None[source]
answer: Any[source]
property chained: bool[source]

At least one tool call was chosen after reading an earlier tool result.

confidence: Any[source]
errors: list[dict[str, Any]][source]
evidence: Any[source]
llm_calls: int[source]
property llm_picks: int[source]

Tool calls an LLM step chose (llm + llm-chained).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property no_information: bool[source]

The agent answered fsr-ai’s “No information available”.

question: str | None[source]
status: str | None[source]
steps: list[str][source]
tokens: dict[str, Any][source]

this run’s own token totals (input_tokens/output_tokens/total_tokens/llm_calls)

tool_calls: list[TracedToolCall][source]
trace_id: str[source]
class pyfsr.models.AgentRunResult(*, task_id: str | None = None, status: str | None = None, answer: Any | None = None, evidence: Any | None = None, confidence: str | None = None, logs: list[dict[str, ~typing.Any]]=<factory>, phases: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

Result of one single-agent run (run_agent()).

A single agent answers one question; it does not run the investigation pipeline, so this is a different shape from InvestigationResult – the keys mirror the agent’s own outputformat (answer / evidence / confidence) rather than summary/hypotheses. phases is present but empty on a single-agent run; it is only populated for a full investigation. Live-verified on 8.0.

answer: Any | None[source]
confidence: str | None[source]
evidence: Any | None[source]
logs: list[dict[str, Any]][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

phases: list[dict[str, Any]][source]
status: str | None[source]
task_id: str | None[source]
class pyfsr.models.AgentToolResult(*, name: str | None = None, status: str | None = None, args: dict[str, ~typing.Any] | None=None, result: Any = None, error: Any = None, mcp_server_id: str | None = None, mcp_server_name: str | None = None, tool_call_id: str | None = None, cached: bool = False, tool_results: list[Any] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

fsr-ai’s ToolResult: the outcome of one tool call an agent made.

It is the output of a TOOL trace span (TraceSpan.tool_result). 8.0.1 added mcp_server_id/mcp_server_name (the registered server that ran the tool; the built-ins show as e.g. "SOC Framework"), tool_call_id (the LLM’s call id, may be None) and cached (answered from fsr-ai’s tool cache rather than a fresh call). Live-verified on 8.0.1.

args: dict[str, Any] | None[source]
cached: bool[source]
error: Any[source]
mcp_server_id: str | None[source]
mcp_server_name: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
result: Any[source]
status: str | None[source]
tool_call_id: str | None[source]
tool_results: list[Any][source]
class pyfsr.models.AgentTurn(*, task_id: str | None = None, status: str | None = None, answer: str | None = None, pending: PendingInput | None = None, request_id: str | None = None, response_id: str | None = None, error: Any = None, phases: Any = None, logs: Any = None, playbook_context: dict[str, Any] | None = None, playbook_steps: Any = None, is_user_input_needed: bool | None = None, **extra_data: Any)[source]

Bases: _Lenient

One turn of an AgentSession (chat or orchestrator).

status is the agent’s own outcome when it reports one (orchestrator: success, success_no_action, partial, failed, denied, awaiting_approval, needs_clarification, rejected, unplannable), else the task status (completed / failed / … or a non-terminal pending/inprogress if the wait timed out).

answer: str | None[source]
property awaiting_approval: bool[source]
error: Any[source]
property is_paused: bool[source]

The turn stopped for a reply (clarification or approval).

is_user_input_needed: bool | None[source]

True when playbook-generator stopped to ask the user something.

logs: Any[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property needs_clarification: bool[source]
pending: PendingInput | None[source]
phases: Any[source]
playbook_context: dict[str, Any] | None[source]

The outline state playbook-generator wants back with “generate steps”.

playbook_steps: Any[source]

The designer steps playbook-generator generated (final turn).

request_id: str | None[source]
response_id: str | None[source]
status: str | None[source]
task_id: str | None[source]
class pyfsr.models.ConnectorMcpCandidates(*, available: list[str] = <factory>, restricted: list[str] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

Which installed connectors can be hosted as an MCP server (GET /mcp/servers/connector).

restricted connectors (internal/system ones, e.g. the agent-communication bridge) can never be hosted. available connectors aren’t yet hosted – once one is, it drops off this list (find it instead via mcp_configs(), filtering on type == "connector").

available: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

restricted: list[str][source]
class pyfsr.models.ExecutionTree(*, root_trace_id: str | None = None, traces: list[Any] = <factory>, root: TraceNode | None = None, **extra_data: Any)[source]

Bases: _Lenient

GET /api/ai/traces/{trace_id}/execution-tree – a run plus every run it submitted.

This is what the Trace Flow panel renders: an investigation fans out into child traces (one per sub-agent run), stitched into one root tree. traces lists the stitched trace ids.

count_by_type() → dict[str, int][source]

{span_type: count} over the whole tree.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

root: TraceNode | None[source]
root_trace_id: str | None[source]
property total_steps: int[source]

Node count – the panel’s Total Steps.

traces: list[Any][source]
class pyfsr.models.FortiAITokenBalance(*, entitled_tokens: int = 0, remain_tokens: int = 0, entitled_topup_tokens: int = 0, remain_topup_tokens: int = 0, account_id: int | None = None, sn: str | None = None, type: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

FortiAI token entitlement for this appliance – see token_balance().

entitled_tokens is the included allowance (5,000,000 a month on 8.0.x), remain_tokens what is left of it; the *_topup_tokens pair counts purchased top-ups. type is DEVICE_LEVEL when the pool belongs to the appliance serial sn.

account_id: int | None[source]
entitled_tokens: int[source]
entitled_topup_tokens: int[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

remain_tokens: int[source]
remain_topup_tokens: int[source]
property remaining: int[source]

Tokens left, allowance plus top-ups.

sn: str | None[source]
type: str | None[source]
property used_tokens: int[source]

Tokens consumed from the allowance and top-ups so far.

class pyfsr.models.InsightExecution(*, task_id: str | None = None, planid: str | None = None, status: str | None = None, result: dict[str, Any] | None = None, execution_log: Any = None, last_executed: str | None = None, insight_id: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

A plan execution: task_id/planid from plan/execute, then its result.

result holds the generated insight (concise_summary, summary, next_action_steps … or error); execution_log the per-step agent outputs.

execution_log: Any[source]
insight_id: str | None[source]

The saved insights record, set by AIInsightsAPI.create(save=True).

last_executed: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

planid: str | None[source]
result: dict[str, Any] | None[source]
status: str | None[source]
task_id: str | None[source]
class pyfsr.models.InsightPlan(*, objective: str | None = None, chain_of_thought: list[str] = <factory>, intent_interpretation: str | None = None, feasibility: dict[str, ~typing.Any] | None=None, entities: list[Any] = <factory>, steps: list[Any] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

An AI Insight plan (8.0.1 POST /api/ai/insight/plan, or an insight template’s plan).

feasibility is {status, reason, clarification_questions}; the UI refuses to run a plan whose status is "infeasible". steps are the agent calls the plan executes, in order.

chain_of_thought: list[str][source]
entities: list[Any][source]
feasibility: dict[str, Any] | None[source]
property feasible: bool[source]
intent_interpretation: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objective: str | None[source]
steps: list[Any][source]
class pyfsr.models.InsightRecord(*, insight_id: str | None = None, title: str | None = None, query: str | None = None, active: bool | None = None, planid: str | None = None, plan: dict[str, Any] | None = None, result: dict[str, Any] | None = None, execution_log: Any = None, last_executed: str | None = None, socrole: str | None = None, scheduleid: str | None = None, createuser: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

A saved AI Insight (GET /api/ai/insight/ / GET /api/ai/insight/{id}).

active: bool | None[source]
createuser: str | None[source]
execution_log: Any[source]
insight_id: str | None[source]
last_executed: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

plan: dict[str, Any] | None[source]
planid: str | None[source]
query: str | None[source]
result: dict[str, Any] | None[source]
scheduleid: str | None[source]
socrole: str | None[source]
title: str | None[source]
class pyfsr.models.InvestigationHandle(*, task_id: str | None = None, status: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

Response of starting/triggering a triage run – {"task_id", "status"}.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

status: str | None[source]
task_id: str | None[source]
class pyfsr.models.InvestigationQuestion(*, index: int | None = None, question: str | None = None, agent: str | None = None, input: Any | None = None, response: Any | None = None, evidence: str | None = None, supports: list[str] = <factory>, weakens: list[str] = <factory>, information_type: Any | None = None, status: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One question/evidence entry – see investigation_questions().

agent: str | None[source]
evidence: str | None[source]
index: int | None[source]
information_type: Any | None[source]
input: Any | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

question: str | None[source]
response: Any | None[source]
status: str | None[source]
supports: list[str][source]
weakens: list[str][source]
class pyfsr.models.InvestigationResult(*, task_id: str | None = None, status: str | None = None, summary: dict[str, ~typing.Any] | None=None, hypotheses: list[dict[str, ~typing.Any]]=<factory>, logs: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

Full triage result/verdict (GET /api/ai/agents/{task_id}/result).

summary/hypotheses/logs are left untyped (Any) – see investigation_questions() and hypothesis_evidence() for the derived, typed views over this payload.

hypotheses: list[dict[str, Any]][source]
logs: list[dict[str, Any]][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

status: str | None[source]
summary: dict[str, Any] | None[source]
task_id: str | None[source]
class pyfsr.models.InvestigationTool(*, avenue: str, source: str, required_field: str = '', active: bool = True)[source]

Bases: BaseModel

One row of the investigation planner’s tool table (8.0.1).

The planner writes one question per active row and sends it to the agent whose description fits source – a custom Triage agent only gets questions when a row names it. See pyfsr.api.ai.AIApi.investigation_tools().

active: bool[source]
avenue: str[source]
model_config = {}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

static parse_table(text: str) → list[InvestigationTool][source]

Parse the markdown table stored in an INFRA_INFO/TOOL_LIST record.

static render_table(rows: list[InvestigationTool]) → str[source]

Render rows as the planner’s markdown table (the stock layout).

required_field: str[source]
source: str[source]
class pyfsr.models.InvestigationTrace(*, task_id: str, status: str | None = None, runs: list[AgentRun] = <factory>, tokens: dict[str, ~typing.Any]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

A whole alert investigation from the tracer store – see investigation().

runs holds the root run (trace_id == task_id) and every sub-agent run it submitted. tokens is the whole investigation’s token totals (input_tokens/output_tokens/total_tokens/llm_calls): the root trace’s tokens/{task_id} figures, which already include every sub-agent run. Each run’s tokens is its own share, so they add up to it.

metrics() → dict[str, Any][source]

How the agents used tools, question by question.

8.0.0 made exactly one LLM-chosen tool call per question and never chained. questions_chained > 0 is the evidence that an agent read a tool result and called again; questions_multi_tool alone mostly counts code-driven lookups.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property questions: list[AgentRun][source]

The runs that answered an investigation question.

runs: list[AgentRun][source]
status: str | None[source]
task_id: str[source]
tokens: dict[str, Any][source]
tokens_by_agent() → dict[str, dict[str, int]][source]

Token totals per agent name, largest first.

property tool_calls: list[TracedToolCall][source]

Every tool call of the investigation, run by run, in time order within a run.

class pyfsr.models.LLMConfig(*, uuid: str | None = None, name: str | None = None, isdefault: bool | None = None, active: bool | None = None, model: str | None = None, modelname: str | None = None, provider: str | None = None, apikey: str | None = None, baseurl: str | None = None, config: dict[str, ~typing.Any]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

A reasoning-profile config (GET /api/ai/llm/config), e.g. Low Reasoning.

config.connector_name/connector_config_id point at the connector configuration backing this profile (e.g. the fortinet-fortiai-proxy proxy).

active: bool | None[source]
apikey: str | None[source]
baseurl: str | None[source]
config: dict[str, Any][source]
isdefault: bool | None[source]
model: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modelname: str | None[source]
name: str | None[source]
provider: str | None[source]
uuid: str | None[source]
class pyfsr.models.LLMProvider(*, uuid: str | None = None, name: str | None = None, label: str | None = None, version: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

An allowed LLM provider – an installed solution pack (/api/ai/llm/allowed-providers).

label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
uuid: str | None[source]
version: str | None[source]
class pyfsr.models.MCPServerConfig(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, url: str | None = None, transport: str | None = None, type: str | None = None, active: bool | None = None, timeout: int | None = None, command: str | None = None, authentication: str | dict[str, Any] | None = None, description: str | None = None, metadata: Any | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A registered MCP server (/api/3/mcp_configurations – the MCPConfiguration module).

authentication is stored server-side as a JSON string (e.g. '{"type":"FSR"}' for built-ins, '{"value": "<bearer token>"}' for a remote server) – left untyped since its shape varies by type. See register_mcp_server() for the encode-on-write convenience and mcp_tool_catalog() for decoding it back to probe tools/list.

active: bool | None[source]
authentication: str | dict[str, Any] | None[source]
command: str | None[source]
description: str | None[source]
metadata: Any | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
timeout: int | None[source]
transport: str | None[source]
type: str | None[source]
url: str | None[source]
class pyfsr.models.MCPServerRef(*, id: str | None = None, name: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One entry from GET /api/ai/mcp – the id+name the agent-config UI lists.

Thinner than MCPServerConfig (no url/transport/auth); resolve to the full record via mcp_configs().

id: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
class pyfsr.models.MCPServerStatus(*, uuid: str | None = None, name: str | None = None, valid: bool = False, error: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One entry from GET /api/ai/mcp/status – the health of a registered MCP server.

Complements MCPServerRef (id+name) and MCPServerConfig (full record): this is the runtime liveness probe the agent UI uses to show green/red per server. valid is the connectivity verdict; error carries the failure reason when it is not.

error: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
uuid: str | None[source]
valid: bool[source]
class pyfsr.models.MCPTool(*, name: str | None = None, description: str | None = None, inputSchema: dict[str, Any] | None = None, **extra_data: Any)[source]

Bases: _Lenient

One tool advertised by an MCP server’s tools/list.

Used both by the registration probe (validate_mcp_server(), which reads the MCP-native inputSchema key) and by the appliance’s own native gateway (list_tools(), whose historical dict shape used input_schema). inputSchema accepts either spelling on the wire and input_schema reads it back either way, so tool["input_schema"]/tool.get("input_schema") and tool.inputSchema all resolve – the dict-style access the tool-surface materializer relies on keeps working.

description: str | None[source]
inputSchema: dict[str, Any] | None[source]
property input_schema: dict[str, Any] | None[source]

Snake-case alias for inputSchema (native-gateway dict shape).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
class pyfsr.models.MCPToolResult(*, status: str | None = None, result: Any = None, error: Any = None, **extra_data: Any)[source]

Bases: _Lenient

The {"status", "result", "error"} envelope a native gateway tool returns.

Every FortiSOAR native tool (/mcp/soc/, /mcp/playbooks/, …) replies with this envelope on success; ok is a convenience for status == "success". In-band tool failures come back as a plain string instead of this envelope – call_tool() returns that raw value untouched, while call_tool_result() always wraps into this model (a non-envelope payload lands under result with status=None). Extra keys are preserved (extra="allow").

error: Any[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property ok: bool[source]

status == "success" – the FortiSOAR-native envelope convention.

Only meaningful for FortiSOAR’s own tools (native gateway / internal registered servers), which reply with {"status": "success", ...}. A third-party MCP server returns its own payload shape (e.g. raw text or its own JSON), so ok is False even on success – read result / error for those.

result: Any[source]
status: str | None[source]
class pyfsr.models.MCPValidateResult(*, valid: bool = False, tools: list[MCPTool] = <factory>, message: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

Response of POST /api/ai/mcp/validate – probing a server before saving.

message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

tools: list[MCPTool][source]
valid: bool[source]
class pyfsr.models.PendingInput(*, pause_id: str | None = None, type: str | None = None, question: str | None = None, expected_type: str | None = None, options: list[dict[str, ~typing.Any]]=<factory>, expires_at: str | None = None, round: int | None = None, **extra_data: Any)[source]

Bases: _Lenient

The open question on a paused chat/orchestrator turn.

type is "clarification" (the planner needs a value – reply with it) or "approval" (the executor wants a yes/no before a state-changing action – reply approve/deny). expected_type is entity, choice or decision; options are the renderable choices.

expected_type: str | None[source]
expires_at: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

options: list[dict[str, Any]][source]
pause_id: str | None[source]
question: str | None[source]
round: int | None[source]
type: str | None[source]
class pyfsr.models.ToolCall(*, tool_name: str | None = None, tool_args: Any | None = None, correlation_id: str | None = None, title: str | None = None, model: str | None = None, latency_ms: int | None = None, server: str | None = None, server_uuid: str | None = None, source: str = 'llm_activity_logs', agent: str | None = None, question: str | None = None, selected_by: str | None = None, output: Any = None, error: Any = None, cached: bool | None = None, span_id: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One MCP/connector tool invocation of an investigation.

source says where it was read from. "llm_activity_logs" (8.0.0; see tool_usage()) carries only the LLM-selected calls, with title/model/latency_ms. "traces" (8.0.1+) carries every call the agents ran, plus agent/question (what it served), selected_by (code/llm/llm-chained; see TracedToolCall), server straight from the trace, and the real output/error.

agent: str | None[source]
cached: bool | None[source]
correlation_id: str | None[source]
error: Any[source]
latency_ms: int | None[source]
model: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

output: Any[source]
question: str | None[source]
selected_by: str | None[source]
server: str | None[source]
server_uuid: str | None[source]
source: str[source]
span_id: str | None[source]
title: str | None[source]
tool_args: Any | None[source]
tool_name: str | None[source]
class pyfsr.models.TraceNode(*, span_id: str, name: str | None = None, span_type: str | None = None, status: str | None = None, start_time: str | None = None, end_time: str | None = None, start_offset_ms: float | None = None, duration_ms: float | None = None, attributes: dict[str, ~typing.Any]=<factory>, child_count: int | None = None, children: list[TraceNode] = <factory>, children_truncated: bool | None = None, io_preview: Any = None, **extra_data: Any)[source]

Bases: _Lenient

A node of a trace tree (.../{trace_id}, .../execution-tree, .../spans/{id}/tree).

Carries a summary of the span (no full I/O – fetch that with span()) plus its children. start_offset_ms is relative to the tree root.

attributes: dict[str, Any][source]
child_count: int | None[source]
children: list[TraceNode][source]
children_truncated: bool | None[source]
duration_ms: float | None[source]
end_time: str | None[source]
find(*, span_type: str | None = None, name: str | None = None) → list[TraceNode][source]

All descendants (including self) matching span_type and/or name.

io_preview: Any[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
span_id: str[source]
span_type: str | None[source]
start_offset_ms: float | None[source]
start_time: str | None[source]
status: str | None[source]
walk(depth: int = 0)[source]

Yield (depth, node) for this node and every descendant, depth-first.

class pyfsr.models.TraceSpan(*, span_id: str, trace_id: str | None = None, parent_span_id: str | None = None, name: str | None = None, span_type: str | None = None, status: str | None = None, start_time: str | None = None, end_time: str | None = None, duration_ms: float | None = None, input: Any = None, output: Any = None, error: Any = None, attributes: dict[str, ~typing.Any]=<factory>, session_id: str | None = None, user_id: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One span (GET /api/ai/traces/spans/{span_id} or a row of .../{trace_id}/spans).

span_type is one of AGENT, LLM, LLM_WRAPPER, TOOL, SKILL_RETRIEVER, DOCUMENT_SEARCH, CUSTOM. input/output keep the raw payload: for an LLM span the prompt is under input.payload.params.messages and the reply under output (content, tools, usage, provider, model).

attributes: dict[str, Any][source]
duration_ms: float | None[source]
end_time: str | None[source]
error: Any[source]
input: Any[source]
property model: str | None[source]

LLM model recorded on the span (output.model). Empty on 8.0.1 for FortiAI-proxy calls.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
output: Any[source]
parent_span_id: str | None[source]
property provider: str | None[source]

LLM provider recorded on the span (output.provider); None for non-LLM spans.

session_id: str | None[source]
span_id: str[source]
span_type: str | None[source]
start_time: str | None[source]
status: str | None[source]
property tool_call: dict[str, Any][source]

{"name", "args"} for a TOOL span (input.tool_call), else {}.

property tool_result: AgentToolResult | None[source]

This span’s output as an AgentToolResult for a TOOL span, else None.

trace_id: str | None[source]
property usage: dict[str, Any][source]

Token usage for an LLM span (prompt_tokens/completion_tokens/total_tokens), else {}.

user_id: str | None[source]
class pyfsr.models.TraceSummary(*, trace_id: str, parent_trace_id: str | None = None, root_span_id: str | None = None, name: str | None = None, status: str | None = None, session_id: str | None = None, user_id: str | None = None, start_time: str | None = None, end_time: str | None = None, total_tokens: int = 0, total_cost: float = 0.0, span_counts: dict[str, int]=<factory>, tags: list[Any] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

One row of GET /api/ai/traces/ – a top-level agent run.

start_time/end_time are ISO-8601 strings; end_time is None while the run is still going (status == "RUNNING"). span_counts is per span type ({"LLM": 2, "TOOL": 1, ...}).

end_time: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
parent_trace_id: str | None[source]
root_span_id: str | None[source]
session_id: str | None[source]
span_counts: dict[str, int][source]
start_time: str | None[source]
status: str | None[source]
tags: list[Any][source]
total_cost: float[source]
total_tokens: int[source]
trace_id: str[source]
user_id: str | None[source]
class pyfsr.models.TracedToolCall(*, span_id: str | None = None, trace_id: str | None = None, tool_name: str | None = None, args: Any = None, server: str | None = None, server_id: str | None = None, status: str | None = None, error: Any = None, cached: bool = False, output: Any = None, result_chars: int = 0, selected_by: str = 'code', agent: str | None = None, question: str | None = None, start_time: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One tool call recovered from a TOOL span, with who chose it.

selected_by says what decided the call should run:

  • "code" – no LLM step asked for it; the agent’s own code ran it (fixed lookups such as get_indicators/enrich_indicator on every IOC).

  • "llm" – an LLM step’s output named it, but that step’s prompt held no earlier tool results: a one-shot pick made blind.

  • "llm-chained" – the choosing LLM step had already been given tool results (role: tool messages): it read a result and called again. Only this is multi-step reasoning over tool output.

server is the registered MCP server that ran it (mcp_server_name; None when fsr-ai refused the tool before dispatch). output is the tool’s result, JSON-decoded when it was JSON text; result_chars is its raw size.

agent: str | None[source]
args: Any[source]
cached: bool[source]
error: Any[source]
property failed: bool[source]

The call errored or was refused (e.g. "Tool not allowed : <name>").

property llm_selected: bool[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

output: Any[source]
question: str | None[source]
result_chars: int[source]
selected_by: str[source]
server: str | None[source]
server_id: str | None[source]
span_id: str | None[source]
start_time: str | None[source]
status: str | None[source]
tool_name: str | None[source]
trace_id: str | None[source]

Agents

class pyfsr.models.Agent(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, agentId: str | None = None, name: str | None = None, active: bool | None = None, description: str | None = None, created: str | None = None, modified: str | None = None, router: RecordIRI | dict[str, Any] | None = None, installerType: PicklistIRI | None = None, configurationHealth: PicklistIRI | None = None, **extra_data: Any)[source]

Bases: BaseRecord

An agent record from GET /api/3/agents or POST /api/3/agents.

Core fields typed; operational metadata (installer bytes, SME config, etc.) preserved in extra.

active: bool | None[source]
agentId: str | None[source]
configurationHealth: PicklistIRI | None[source]
created: str | None[source]
description: str | None[source]
installerType: PicklistIRI | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified: str | None[source]
name: str | None[source]
router: RecordIRI | dict[str, Any] | None[source]
uuid: str | None[source]
class pyfsr.models.AgentConnectorStatus(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, agent: str | None = None, agentId: str | None = None, name: str | None = None, version: str | None = None, status: str | None = None, label: str | None = None, errorMessage: str | None = None, progressPercent: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A single row from connector_install_status().

Returned by POST /api/integration/connectors/agents/<name>/<version>/. status progresses through "awaiting" → "in-progress" → "Completed".

agent: str | None[source]
agentId: str | None[source]
errorMessage: str | None[source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
progressPercent: int | None[source]
status: str | None[source]
version: str | None[source]

AI agent packages

class pyfsr.models.AgentInfo(*, name: str, label: str | None = None, agentclass: str | None = None, version: str = '1.0.0', description: str | None = None, publisher: str | None = None, cs_approved: bool | None = None, cs_compatible: bool | None = None, contributor: str | None = None, category: str | None = None, icon_small_name: str | None = None, icon_large_name: str | None = None, tags: list[str] = <factory>, fsrMinCompatibility: str | None = None, help_online: str | None = None, additional_information: list[dict[str, ~typing.Any]]=<factory>, inputformat: dict[str, ~typing.Any]=<factory>, outputformat: dict[str, ~typing.Any]=<factory>, configuration: dict[str, ~typing.Any]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

The info.json manifest of an AI agent package.

agentclass must name a class defined in agent.py (and be unique among installed agents); AgentPackage cross-checks it and the required fields. pack_agent() names the zip’s top folder after name. configuration.fields is the per-agent config form the FortiSOAR UI renders (config-type toggle, LLM-provider picker, MCP-server multiselect, masking agent) – left untyped here as it’s a free-form field schema.

additional_information: list[dict[str, Any]][source]
agentclass: str | None[source]
category: str | None[source]
configuration: dict[str, Any][source]
contributor: str | None[source]
cs_approved: bool | None[source]
cs_compatible: bool | None[source]
description: str | None[source]
fsrMinCompatibility: str | None[source]
help_online: str | None[source]
icon_large_name: str | None[source]
icon_small_name: str | None[source]
inputformat: dict[str, Any][source]
label: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str[source]
outputformat: dict[str, Any][source]
publisher: str | None[source]
tags: list[str][source]
version: str[source]
class pyfsr.models.AgentMemory(*, allowed_tools: dict[str, list[str]]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

config/memory.yaml – the agent’s MCP-tool allowlist.

allowed_tools maps a registered MCP-configuration uuid (see client.ai.mcp_configs()) to the list of tool names on that server the agent may call. An empty list means “server is bound but no tools yet allowed”; the key must be a uuid that actually resolves on the target appliance or the binding is inert.

allowed_tools: dict[str, list[str]][source]
mcp_configuration_uuids() → list[str][source]

The MCP-configuration uuids this agent is wired to.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.AgentPackage(*, info: AgentInfo, prompts: AgentPromptFile = <factory>, memory: AgentMemory = <factory>, files: list[str] = <factory>, requirements: str | None = None, agent_source: str | None = None)[source]

Bases: BaseModel

A fully-parsed AI agent package: manifest + prompts + memory + file list.

Build one with from_dir() to validate a source folder before packing, or construct directly. validate_consistency() catches the mistakes that fail silently on the appliance rather than at upload:

  • a manifest field fsr-ai stores NOT NULL missing (label, agentclass, category, publisher) – the import then fails half-way and leaves ai-agents/<name>_<version>/ behind, which blocks every later import of that name, replace=true included (8.0.1);

  • agent.py missing, or not defining the class named by agentclass;

  • a prompt uuid referenced in agent.py that prompt.yaml doesn’t define;

  • icons named in the manifest that aren’t in the package.

agent_source: str | None[source]

Source of agent.py when known – used to check agentclass and cross-check referenced prompt uuids.

files: list[str][source]

Package-relative file paths present in the bundle (e.g. "agent.py").

classmethod from_dir(source_dir: str) → AgentPackage[source]

Parse and validate an agent package from a source directory.

source_dir is the package root (the folder that is the agent, e.g. .../metric-computation). Reads info.json (required), prompt.yaml and config/memory.yaml (both optional), and records the file list + agent.py source. Raises on a missing/invalid manifest or a failed consistency check.

info: AgentInfo[source]
memory: AgentMemory[source]
model_config = {'arbitrary_types_allowed': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

prompts: AgentPromptFile[source]
requirements: str | None[source]

Contents of requirements.txt when the package has one – checked against REQUIREMENTS_BLOCKED_PATTERNS.

validate_consistency() → None[source]

Raise ValueError on package defects that fail silently on-box.

Checks the agentclass is defined in agent.py, every prompt uuid the source references exists in prompt.yaml, and manifest-named icons are present, and that requirements.txt passes the 8.0.1 import check (see requirements_problems()). A no-op for fields it can’t see (e.g. no agent_source).

class pyfsr.models.AgentPrompt(*, name: str | None = None, system_instruction: str | None = None, user_instruction: str | None = None, validation_instruction: str | None = None, response_format: Any | None = None, description: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One entry in prompt.yaml’s prompts map (keyed by a uuid).

agent.py pulls a prompt by that uuid (self.get_prompt_by_uuid(...)) and .format(**inputs) s system_instruction / user_instruction – so any {placeholder} in those strings must be supplied at call time.

description: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
response_format: Any | None[source]
system_instruction: str | None[source]
user_instruction: str | None[source]
validation_instruction: str | None[source]
class pyfsr.models.AgentPromptFile(*, prompts: dict[str, ~pyfsr.models._ai_agent_package.AgentPrompt]=<factory>, **extra_data: Any)[source]

Bases: _Lenient

The whole prompt.yaml: {"prompts": {<uuid>: AgentPrompt}}.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

prompts: dict[str, AgentPrompt][source]

Rules

class pyfsr.models.DeliveryRule(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, entity_type: str | None = None, event_type: str | None = None, event_source: str | None = None, trigger_condition: dict[str, Any] | None = None, actions: list[dict[str, Any]] | None = None, is_system: bool | None = None, is_active: bool | None = None, visible: bool | None = None, priority: int | None = None, category: str | None = None, source: Any | None = None, channel_preference_field: Any | None = None, expiry: Any | None = None, entity_id: Any | None = None, parent_rule: Any | None = None, workflow: Any | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A delivery rule from the rule engine (GET /rule/api/rules/).

The notification rules the SOAR UI lists under Rules: each pairs a trigger_condition (a crudhub-style filter over entity_type records) with actions[] that fire on match, every action naming the channel_uuid it delivers through (see RuleChannel).

Rule-engine objects carry no JSON-LD envelope, so iri is None here — uuid is the only identifier. is_system marks the rules FortiSOAR ships; those exist on every appliance and are the safest ones to reference in a portable export template.

actions: list[dict[str, Any]] | None[source]
category: str | None[source]
channel_preference_field: Any | None[source]
entity_id: Any | None[source]
entity_type: str | None[source]
event_source: str | None[source]
event_type: str | None[source]
expiry: Any | None[source]
is_active: bool | None[source]
is_system: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
parent_rule: Any | None[source]
priority: int | None[source]
source: Any | None[source]
trigger_condition: dict[str, Any] | None[source]
uuid: str | None[source]
visible: bool | None[source]
workflow: Any | None[source]
class pyfsr.models.PreprocessingRule(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, description: str | None = None, entityType: str | None = None, applicableOn: str | None = None, isActive: bool | None = None, priority: int | None = None, criteria: dict[str, Any] | None = None, action: dict[str, Any] | None = None, actionType: dict[str, Any] | str | None = None, endDate: float | None = None, skipPlaybookExecution: Any | None = None, recordTags: list[Any] | None = None, createUser: str | dict[str, Any] | None = None, createDate: float | None = None, modifyUser: str | dict[str, Any] | None = None, modifyDate: float | None = None, id: int | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A preprocessing rule (GET /api/3/preprocessing_rules).

Rules that run against records as they arrive (applicableOn: "incoming") to dedupe, link, or update them before playbooks fire. Unlike DeliveryRule and RuleChannel this is a crudhub record — JSON-LD envelope and camelCase fields — so iri is populated.

criteria holds the match condition (plus a days lookback window) and action describes what to do on match (link / update).

action: dict[str, Any] | None[source]
actionType: dict[str, Any] | str | None[source]
applicableOn: str | None[source]
createDate: float | None[source]
createUser: str | dict[str, Any] | None[source]
criteria: dict[str, Any] | None[source]
description: str | None[source]
endDate: float | None[source]
entityType: str | None[source]
id: int | None[source]
isActive: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modifyDate: float | None[source]
modifyUser: str | dict[str, Any] | None[source]
name: str | None[source]
priority: int | None[source]
recordTags: list[Any] | None[source]
skipPlaybookExecution: Any | None[source]
class pyfsr.models.RuleChannel(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, name: str | None = None, type: str | None = None, description: str | None = None, config: dict[str, Any] | None = None, is_active: bool | None = None, default_params: dict[str, Any] | None = None, **extra_data: Any)[source]

Bases: BaseRecord

A rule channel from the rule engine (GET /rule/api/channel/).

The delivery transport a DeliveryRule action targets by channel_uuid — e.g. In-App Notifications, email. type is "system" for the built-in channels. Like DeliveryRule, this is not a crudhub record: no JSON-LD envelope, uuid is the identifier.

config: dict[str, Any] | None[source]
default_params: dict[str, Any] | None[source]
description: str | None[source]
is_active: bool | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
type: str | None[source]
uuid: str | None[source]

Schedules

class pyfsr.models.CrontabScheduleModel(*, id: int | None = None, minute: str | None = None, hour: str | None = None, day_of_month: str | None = None, month_of_year: str | None = None, day_of_week: str | None = None, timezone: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The nested crontab on a ScheduledTask.

day_of_month: str | None[source]
day_of_week: str | None[source]
hour: str | None[source]
id: int | None[source]
minute: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

month_of_year: str | None[source]
timezone: str | None[source]
class pyfsr.models.ScheduledTask(*, id: str | None = None, name: str | None = None, crontab: CrontabScheduleModel | None = None, interval: Any | None = None, task: str | None = None, args: str | None = None, kwargs: dict[str, Any] | None = None, queue: str | None = None, exchange: str | None = None, routing_key: str | None = None, headers: str | None = None, priority: Any | None = None, expires: str | None = None, expire_seconds: int | None = None, one_off: bool | None = None, start_time: str | None = None, enabled: bool | None = None, last_run_at: str | None = None, total_run_count: int | None = None, date_changed: str | None = None, description: str | None = None, solar: Any | None = None, clocked: Any | None = None, **extra_data: Any)[source]

Bases: ApiResult

A django-celery-beat PeriodicTask from /api/wf/api/scheduled/.

id is a per-request Fernet token (not a stable primary key) — always look a task up by name before writing it back, per SchedulesAPI’s module docstring. kwargs carries the workflow-specific payload (wf_iri, exit_if_running, schedule_id, …) and is left untyped since its shape varies by task.

args: str | None[source]
clocked: Any | None[source]
crontab: CrontabScheduleModel | None[source]
date_changed: str | None[source]
description: str | None[source]
enabled: bool | None[source]
exchange: str | None[source]
expire_seconds: int | None[source]
expires: str | None[source]
headers: str | None[source]
id: str | None[source]
interval: Any | None[source]
kwargs: dict[str, Any] | None[source]
last_run_at: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
one_off: bool | None[source]
priority: Any | None[source]
queue: str | None[source]
routing_key: str | None[source]
solar: Any | None[source]
start_time: str | None[source]
task: str | None[source]
total_run_count: int | None[source]

Widgets

class pyfsr.models.WidgetRecord(*, id_iri: str | None = None, uuid: str | None = None, name: str | None = None, version: str | None = None, title: str | None = None, subTitle: str | None = None, draft: bool | None = None, installed: bool | None = None, enablePublish: bool | None = None, metadata: dict | None = None, **extra_data: Any)[source]

Bases: ApiResult

A widget record from client.widgets (upload/publish/list/get).

Dict-compatible, so record["uuid"] works alongside record.uuid. Fields not modeled here (tree, layout metadata, …) stay in extra.

draft: bool | None[source]
enablePublish: bool | None[source]
id_iri: str | None[source]
installed: bool | None[source]
metadata: dict | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
property published: bool[source]

True once the widget is live – installed and no longer a draft.

subTitle: str | None[source]
title: str | None[source]
uuid: str | None[source]
version: str | None[source]

App configuration

class pyfsr.models.NavItem(*, title: str | None = None, icon: str | None = None, state: NavState | None = None, require: NavRequire | list[Any] | None = None, items: list[NavItem] | None = None, editMode: bool | None = None, exists: bool | None = None, include: bool | None = None, isEnabled: bool | None = None, open: bool | None = None, openStatus: bool | None = None, mergeType: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

A single navigation entry — a leaf or a group.

A leaf binds a module through state / require. A group carries child entries under items and has no state/require. All fields are optional because the wire shape differs between the two and between appliance versions; unknown keys are preserved for round-tripping.

edit_mode: bool | None[source]
exists: bool | None[source]
icon: str | None[source]
include: bool | None[source]
is_enabled: bool | None[source]
property is_group: bool[source]

True if this entry has children (i.e. is a menu group).

items: list[NavItem] | None[source]
merge_type: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

open: bool | None[source]
open_status: bool | None[source]
require: NavRequire | list[Any] | None[source]
state: NavState | None[source]
title: str | None[source]
class pyfsr.models.NavRequire(*, module: str | None = None, action: str | None = None, **extra_data: Any)[source]

Bases: ApiResult

The visibility gate on a navigation leaf.

{"module": "alerts", "action": "read"} means the leaf is shown only to users with the read permission on the alerts module. Groups carry no require (it comes back None / absent), and an empty array [] on a leaf means unrestricted — see NavItem.require.

action: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

module: str | None[source]
class pyfsr.models.NavState(*, name: str | None = None, parameters: dict[str, Any] | list[Any] | None = None, **extra_data: Any)[source]

Bases: ApiResult

The Angular UI-router state a navigation leaf routes to.

parameters is usually {"module": "<name>"} for module-list entries, but comes back as an empty list [] for parameterless states (e.g. the dashboard), so it is typed permissively.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None[source]
parameters: dict[str, Any] | list[Any] | None[source]

Base classes

class pyfsr.models.BaseRecord(*, id_iri: str | None = None, record_type: str | None = None, uuid: str | None = None, **extra_data: Any)[source]

Bases: BaseModel

Dict-compatible base for typed FortiSOAR records.

Every concrete entity model (Alert, Incident, Task, Comment, …) subclasses this. Modules without a registered model are parsed into a bare BaseRecord so callers still get IRI/uuid helpers and dict access.

as_record(field: str, model: type[BaseModel]) → Any[source]

Coerce relationship field into model, whether expanded or an IRI.

A single-relationship field comes back either as a bare IRI string (not expanded) or as the full nested object (relationships pulled). This normalizes both into a model instance — an IRI string yields a thin instance carrying only @id (so .iri works) — and returns None when the field is absent/null.

property assigned_to: User | None[source]

The assignee as a User, or None.

Reads assignedTo (alerts/incidents) and falls back to assignedToPerson (tasks).

property create_user: Actor | None[source]

The createUser as a User or Appliance.

Dispatches on @type: "Appliance" records (playbook-engine actors) return an Appliance, "ApiKey" records (a record created via an API key) return an ApiKey, and everything else ("Person") returns a User. All share BaseRecord so .iri and .uuid always work.

get(key: str, default: Any = None) → Any[source]

Dict-style accessor: value for key (by name or @-alias) or default.

id_iri: str | None[source]
property iri: str | None[source]

The record’s @id IRI (e.g. /api/3/alerts/<uuid>).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property modify_user: Actor | None[source]

The modifyUser as a User or Appliance.

See create_user for dispatch logic.

property owner_teams: list[Any][source]

The owners relationship as a list of Team.

picklist_uuid(field: str) → str | None[source]

Return the trailing uuid of a picklist/relationship IRI field.

Picklist and single-relationship fields hold an IRI like /api/3/picklists/<uuid>; this pulls out the <uuid> tail. Returns None when the field is absent or not a string IRI.

record_type: str | None[source]
to_dict(*, by_alias: bool = True, exclude_none: bool = False, serialize_special: bool = False) → dict[str, Any][source]

Serialize back to a plain FortiSOAR-shaped dict.

Defaults to by_alias=True so @id/@type round-trip with their wire names.

When serialize_special=True, object/array fields (typed as list[Any] or dict[str, Any]) are JSON-encoded to strings for wire submission, as FortiSOAR expects. When False (the default), they remain as native Python objects for backward compatibility.

uuid: str | None[source]

_audit

class pyfsr.models.AuditActivity(*, operation: str | None = None, transaction_date: int | None = None, user: str | None = None, user_id: str | None = None, playbook_name: str | None = None, playbook_iri: str | None = None, entity_type: str | None = None, entity_uuid: str | None = None, display_name: str | None = None, title: str | None = None, component: str | None = None, source: str | None = None, data: dict[str, Any] | None = None, link_entity_details: dict[str, Any] | None = None, id: int | str | None = None, **extra_data: Any)[source]

Bases: ApiResult

One audit-log entry — a single change event on a record.

Carries: operation (Create/Update/Link/Unlink/Comment/Trigger/…), transaction_date (epoch ms), user ("Playbook" for playbook changes), playbook_name / playbook_iri (when a playbook did it), entity_type, entity_uuid, title, and data (linked entity details, old/new values, etc.).

property by_playbook: bool[source]

True when a playbook made this change (user == "Playbook").

component: str | None[source]
data: dict[str, Any] | None[source]
display_name: str | None[source]
entity_type: str | None[source]
entity_uuid: str | None[source]
id: int | str | None[source]
property linked_entity_display: str | None[source]

The display name of the linked entity, or None.

property linked_entity_iri: str | None[source]

The IRI of the entity linked/unlinked (linkEntityDetails.iri), or None.

property linked_entity_type: str | None[source]

The type of the linked entity (indicators, assets…), or None.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operation: str | None[source]
playbook_iri: str | None[source]
playbook_name: str | None[source]
source: str | None[source]
property timestamp_iso: str | None[source]

transaction_date as an ISO-8601 string, or None.

title: str | None[source]
transaction_date: int | None[source]
user: str | None[source]
user_id: str | None[source]
class pyfsr.models.ExecutionContext(*, run_pk: str | None = None, run_name: str | None = None, run_status: str | None = None, run_created: str | None = None, run_modified: str | None = None, record_uuid: str | None = None, record_iri: str | None = None, entity_type: str | None = None, concurrent_changes: list[LifecycleEntry] = [], concurrent_runs: list[dict[str, Any]] = [], before_changes: list[LifecycleEntry] = [], window_seconds: int = 60, **extra_data: Any)[source]

Bases: ApiResult

What was happening to a record around the time of a specific playbook run.

Returned by client.audit.execution_context. Answers the debugging question “why did this playbook see state X when I expected state Y?” by showing what other playbooks or manual actions changed the record within the run’s time window.

concurrent_changes are audit events on the same record that happened during the run (between its created and modified timestamps, ± a buffer). concurrent_runs are other playbook executions on the same record in the same window. before_changes are audit events just before the run started (context for what state the playbook saw).

before_changes: list[LifecycleEntry][source]
concurrent_changes: list[LifecycleEntry][source]
concurrent_runs: list[dict[str, Any]][source]
entity_type: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property other_playbooks: list[str][source]

Playbook names (excluding the run itself) that changed the record.

record_iri: str | None[source]
record_uuid: str | None[source]
run_created: str | None[source]
run_modified: str | None[source]
run_name: str | None[source]
run_pk: str | None[source]
run_status: str | None[source]
summary() → str[source]

A one-line summary suitable for agent output.

window_seconds: int[source]
class pyfsr.models.LifecycleEntry(*, timestamp_ms: int | None = None, kind: str | None = None, operation: str | None = None, user: str | None = None, playbook_name: str | None = None, title: str | None = None, entity_type: str | None = None, entity_uuid: str | None = None, linked_entity_iri: str | None = None, linked_entity_type: str | None = None, linked_entity_display: str | None = None, execution_pk: str | None = None, execution_status: str | None = None, raw: dict[str, Any] | None = None, **extra_data: Any)[source]

Bases: ApiResult

One entry in a record’s lifecycle timeline (from lifecycle()).

A unified view of either an audit-log change or a playbook execution, sorted by timestamp. kind distinguishes the source: "audit" for a field change / link / comment, "execution" for a playbook run.

entity_type: str | None[source]
entity_uuid: str | None[source]
execution_pk: str | None[source]
execution_status: str | None[source]
kind: str | None[source]
linked_entity_display: str | None[source]
linked_entity_iri: str | None[source]
linked_entity_type: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operation: str | None[source]
playbook_name: str | None[source]
raw: dict[str, Any] | None[source]
property timestamp_iso: str | None[source]

Timestamp as an ISO-8601 string, or None.

timestamp_ms: int | None[source]
title: str | None[source]
user: str | None[source]
class pyfsr.models.RecordLifecycle(*, entity_uuid: str | None = None, entity_type: str | None = None, entries: list[LifecycleEntry] = [], audit_count: int = 0, execution_count: int = 0, **extra_data: Any)[source]

Bases: ApiResult

The full change-history timeline for a record (from lifecycle()).

Combines audit-log entries (field changes, links, comments) with playbook executions into a single sorted timeline. entries is oldest-first; by_playbook / state_changes provide filtered views.

audit_count: int[source]
property by_playbook: list[LifecycleEntry][source]

Entries caused by a playbook (user == "Playbook" or kind == "execution").

property comments: list[LifecycleEntry][source]

Audit entries with operation Comment.

entity_type: str | None[source]
entity_uuid: str | None[source]
entries: list[LifecycleEntry][source]
execution_count: int[source]
property field_changes: list[LifecycleEntry][source]

Audit entries with operation Update (field-level changes).

Audit entries with operation Link or Unlink.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property playbook_names: list[str][source]

Distinct playbook names that touched this record.

summary() → str[source]

A one-line summary suitable for agent output.

_stix

class pyfsr.models.StixAttackPattern(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, kill_chain_phases: list[dict[str, ~typing.Any]]=<factory>, external_references: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Attack Pattern SDO.

Variables:
  • kill_chain_phases (list[dict[str, Any]]) – kill-chain phase entries.

  • external_references (list[dict[str, Any]]) – MITRE ATT&CK technique IDs, etc.

external_references: list[dict[str, Any]][source]
kill_chain_phases: list[dict[str, Any]][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.StixBundle(*, type: str = 'bundle', id: str | None = None, objects: list[StixObject] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

A STIX 2.1 Bundle – the top-level container for STIX objects.

The object that FortiSOAR’s POST /api/ingest-feeds/stix-bundle accepts and that the TAXII objects endpoint can return (wrapped in the TaxiiObjectsEnvelope).

Variables:
  • type (str) – always "bundle".

  • id (str | None) – bundle identifier ("bundle--<uuid>").

  • objects (list[StixObject]) – the STIX objects inside, parsed into typed subclasses when possible (falls back to StixObject for unknown types).

id: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objects: list[StixObject][source]
type: str[source]
class pyfsr.models.StixCampaign(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, aliases: list[str] = <factory>, first_seen: str | None = None, last_seen: str | None = None, objectives: str | None = None, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Campaign SDO.

Variables:
  • aliases (list[str]) – alternative names.

  • first_seen (str | None) – when the campaign was first observed.

  • last_seen (str | None) – when the campaign was last observed.

  • objectives (str | None) – campaign objectives.

aliases: list[str][source]
first_seen: str | None[source]
last_seen: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objectives: str | None[source]
class pyfsr.models.StixIndicator(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, pattern_type: str | None = None, valid_from: str | None = None, valid_until: str | None = None, labels: list[str] = <factory>, kill_chain_phases: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Indicator SDO.

Variables:
  • pattern_type (str | None) – pattern language ("stix", "pcre").

  • valid_from (str | None) – when the indicator is first considered valid.

  • valid_until (str | None) – when the indicator is no longer considered valid.

  • labels (list[str]) – open-vocab labels (e.g. ["malicious-activity"]).

  • kill_chain_phases (list[dict[str, Any]]) – kill-chain phase entries.

kill_chain_phases: list[dict[str, Any]][source]
labels: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

pattern_type: str | None[source]
valid_from: str | None[source]
valid_until: str | None[source]
class pyfsr.models.StixIntrusionSet(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, aliases: list[str] = <factory>, first_seen: str | None = None, last_seen: str | None = None, goals: list[str] = <factory>, resource_level: str | None = None, primary_motivation: str | None = None, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Intrusion Set SDO.

Variables:
  • aliases (list[str]) – alternative names.

  • first_seen (str | None) – when the intrusion set was first observed.

  • last_seen (str | None) – when the intrusion set was last observed.

  • goals (list[str]) – intrusion-set goals.

  • resource_level (str | None) – resource level ("individual", "organization").

  • primary_motivation (str | None) – primary motivation ("financial", …).

aliases: list[str][source]
first_seen: str | None[source]
goals: list[str][source]
last_seen: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

primary_motivation: str | None[source]
resource_level: str | None[source]
class pyfsr.models.StixMalware(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, is_family: bool | None = None, malware_types: list[str] = <factory>, kill_chain_phases: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Malware SDO.

Variables:
  • is_family (bool | None) – whether this is a malware family (True) or instance.

  • malware_types (list[str]) – open-vocab types (["ransomware", ["trojan"]).

  • kill_chain_phases (list[dict[str, Any]]) – kill-chain phase entries.

is_family: bool | None[source]
kill_chain_phases: list[dict[str, Any]][source]
malware_types: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class pyfsr.models.StixObject(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

Base for every STIX 2.1 object (SDO, SCO, SRO).

Carries the common STIX 2.1 properties. FortiSOAR’s TAXII server also stamps a scalar value (the indicator value) and leaves pattern null, so those are exposed here rather than only on StixIndicator.

Variables:
  • type (str) – STIX object type ("indicator", "malware", "threat-actor", …).

  • id (str | None) – STIX identifier ("<type>--<uuid>").

  • spec_version (str | None) – STIX spec version ("2.1").

  • created (str | None) – creation timestamp (ISO 8601).

  • modified (str | None) – last-modified timestamp (ISO 8601).

  • name (str | None) – human-readable name.

  • description (str | None) – longer description.

  • value (str | None) – FortiSOAR’s scalar indicator value (an IP, hash, domain, etc.). Present on objects served from the TAXII endpoint; null on standard STIX SDOs that use pattern instead.

  • pattern (str | None) – STIX pattern expression. Typically null on FortiSOAR-served objects (value carries the indicator instead).

created: str | None[source]
description: str | None[source]
id: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

modified: str | None[source]
name: str | None[source]
pattern: str | None[source]
spec_version: str | None[source]
type: str[source]
value: str | None[source]
class pyfsr.models.StixReport(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, published: str | None = None, report_types: list[str] = <factory>, object_refs: list[str] = <factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Report SDO.

Variables:
  • published (str | None) – publication timestamp.

  • report_types (list[str]) – open-vocab types (["threat-report", …).

  • object_refs (list[str]) – STIX IDs of the objects this report references.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

object_refs: list[str][source]
published: str | None[source]
report_types: list[str][source]
class pyfsr.models.StixThreatActor(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, threat_actor_types: list[str] = <factory>, aliases: list[str] = <factory>, first_seen: str | None = None, last_seen: str | None = None, kill_chain_phases: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Threat Actor SDO.

Variables:
  • threat_actor_types (list[str]) – open-vocab types (["nation-state", …).

  • aliases (list[str]) – alternative names.

  • first_seen (str | None) – when the actor was first observed.

  • last_seen (str | None) – when the actor was last observed.

  • kill_chain_phases (list[dict[str, Any]]) – kill-chain phase entries.

aliases: list[str][source]
first_seen: str | None[source]
kill_chain_phases: list[dict[str, Any]][source]
last_seen: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

threat_actor_types: list[str][source]
class pyfsr.models.StixTool(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, tool_types: list[str] = <factory>, kill_chain_phases: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Tool SDO.

Variables:
  • tool_types (list[str]) – open-vocab types (["hacking", …).

  • kill_chain_phases (list[dict[str, Any]]) – kill-chain phase entries.

kill_chain_phases: list[dict[str, Any]][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

tool_types: list[str][source]
class pyfsr.models.StixVulnerability(*, type: str, id: str | None = None, spec_version: str | None = None, created: str | None = None, modified: str | None = None, name: str | None = None, description: str | None = None, value: str | None = None, pattern: str | None = None, cve: str | None = None, external_references: list[dict[str, ~typing.Any]]=<factory>, **extra_data: Any)[source]

Bases: StixObject

A STIX 2.1 Vulnerability SDO.

Variables:
  • cve (str | None) – CVE identifier, when available.

  • external_references (list[dict[str, Any]]) – CVE / NVD references.

cve: str | None[source]
external_references: list[dict[str, Any]][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

_taxii

class pyfsr.models.StixBundleResult(*, status: str | None = None, message: str | None = None, objects_processed: int | None = None, **extra_data: Any)[source]

Bases: _Lenient

Response of POST /api/ingest-feeds/stix-bundle.

Distinct from FeedIngestResult: the STIX bundle endpoint returns message and objects_processed instead of a uuids list, because the bundle fans out into multiple record types.

Variables:
  • status (str | None) – "success" or an error indicator.

  • message (str | None) – human-readable status message.

  • objects_processed (int | None) – number of STIX objects ingested from the bundle.

message: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objects_processed: int | None[source]
property ok: bool[source]

True iff the server reported status == "success".

status: str | None[source]
class pyfsr.models.TaxiiCollection(*, id: str | None = None, title: str | None = None, description: str | None = None, can_read: bool | None = None, can_write: bool | None = None, media_types: list[str] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

One TAXII collection entry.

Served both in the collection list and as a single-collection response.

Variables:
  • id (str | None) – collection identifier (maps to a SystemQuery uuid on FortiSOAR – a dataset is a TAXII collection).

  • title (str | None) – human-readable title.

  • description (str | None) – longer description.

  • can_read (bool | None) – caller may read objects from this collection.

  • can_write (bool | None) – caller may add objects to this collection (typically False on FortiSOAR – publishing is via system_queries).

  • media_types (list[str]) – accepted media types (["application/stix+json;version=2.1"]).

can_read: bool | None[source]
can_write: bool | None[source]
description: str | None[source]
id: str | None[source]
media_types: list[str][source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

title: str | None[source]
class pyfsr.models.TaxiiDiscovery(*, title: str | None = None, description: str | None = None, default: str | None = None, versions: list[str] = <factory>, max_content_length: int | None = None, **extra_data: Any)[source]

Bases: _Lenient

TAXII discovery response (GET /api/taxii/1/).

The server descriptor clients call first to confirm protocol compatibility.

Variables:
  • title (str | None) – server title (e.g. "FortiSOAR TAXII Server").

  • description (str | None) – server description.

  • default (str | None) – default collection URL (/api/taxii/1/collections/).

  • versions (list[str]) – supported TAXII versions.

  • max_content_length (int | None) – max content length the server accepts (bytes).

default: str | None[source]
description: str | None[source]
max_content_length: int | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

title: str | None[source]
versions: list[str][source]
class pyfsr.models.TaxiiManifest(*, objects: list[TaxiiManifestEntry] = <factory>, totalItems: int | None = None, **extra_data: Any)[source]

Bases: _Lenient

Collection manifest response (GET .../collections/{id}/manifest).

One entry per object, no bodies. Cheap “what’s new since X” poll.

Variables:
  • objects (list[TaxiiManifestEntry]) – manifest entries (metadata for each object in the collection).

  • total_items (int | None) – total count (FortiSOAR-specific, may be absent).

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objects: list[TaxiiManifestEntry][source]
total_items: int | None[source]
class pyfsr.models.TaxiiManifestEntry(*, id: str | None = None, date_added: str | None = None, version: str | None = None, media_type: str | None = None, **extra_data: Any)[source]

Bases: _Lenient

One entry in a collection manifest – metadata only, no object body.

Variables:
  • id (str | None) – STIX object id ("malware--<uuid>").

  • date_added (str | None) – when the object was added to the collection (ISO 8601).

  • version (str | None) – object version timestamp (ISO 8601).

  • media_type (str | None) – media type ("application/stix+json;version=2.1").

date_added: str | None[source]
id: str | None[source]
media_type: str | None[source]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

version: str | None[source]
class pyfsr.models.TaxiiObjectsEnvelope(*, totalItems: int | None = None, objects: list[StixObject] = <factory>, **extra_data: Any)[source]

Bases: _Lenient

STIX objects envelope from a TAXII collection.

FortiSOAR’s non-standard {totalItems, objects: []} wrapper (no TAXII 2.1 more / next cursor). Paginate with limit + added_after.

Variables:
  • total_items (int | None) – total object count in the collection.

  • objects (list[StixObject]) – STIX objects, parsed into typed subclasses when possible.

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}[source]

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objects: list[StixObject][source]
total_items: int | None[source]