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
createDate: float | None
createUser: str | dict[str, Any] | None
description: str | None
dueDate: int | None
id: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: RecordIRI | dict[str, Any] | None
name: str | None
severity: PicklistIRI | None
source: str | None
sourceId: str | None
status: PicklistIRI | None
type: PicklistIRI | None
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
approvals: list[Any] | None
assets: list[Any] | None
attachments: list[Any] | None
cVEs: list[Any] | None
campaigns: list[Any] | None
communication: list[Any] | None
content: str | None
devices: list[Any] | None
events: list[Any] | None
file: str | None
file1: str | None
file2: str | None
file3: str | None
file4: str | None
hunt: list[Any] | None
incidents: list[Any] | None
indicators: list[Any] | None
isDeleted: str | None
isImportant: bool | None
lastReplyDate: float | None
managers: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

people: list[Any] | None
peopleUpdated: bool | None
rawCommentData: str | None
replies: list[Any] | None
replyTo: Any | None
scans: list[Any] | None
scenario: list[Any] | None
tasks: list[Any] | None
threatActors: list[Any] | None
threatIntelReports: list[Any] | None
type: PicklistIRI | None
vulnerabilities: list[Any] | None
warrooms: list[Any] | None
workspaces: list[Any] | None
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
ackDueDate: float | None
ackSLApausedon: float | None
affectedHost: str | None
affectedUser: str | None
aftermathDate: float | None
alerts: list[Any] | None
assets: list[Any] | None
assigneddate: float | None
attachments: list[Any] | None
businessImpact: str | None
c2server: str | None
cVEs: list[Any] | None
campaigns: list[Any] | None
category: PicklistIRI | None
comments: list[Any] | None
communications: list[Any] | None
companies: list[Any] | None
confirmationDate: float | None
containmentDate: float | None
containmentTime: int | None
dLLName: str | None
dateOfIncident: float | None
deliveryVector: PicklistIRI | None
description: str | None
destinationIP: str | None
deviceUID: str | None
discoveredOn: float | None
dwellTime: int | None
eradicationDate: float | None
escalated: str | None
fileName: str | None
filehash: str | None
identificationDate: float | None
impactAssessments: str | None
impactROI: int | None
incRemainingAckSLA: int | None
incRemainingRespSLA: int | None
incidentLead: RecordIRI | User | None
incidentphase: str | None
incidentsummary: str | None
indicators: list[Any] | None
metrics: str | None
mitreattackid: str | None
mitregroups: list[Any] | None
mitremitigations: list[Any] | None
mitresoftware: list[Any] | None
mitresubtechniques: list[Any] | None
mitretactics: list[Any] | None
mitretechniques: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
nextsteps: str | None
otherLogs: str | None
pcapFile: str | None
persons: list[Any] | None
phase: PicklistIRI | None
processName: str | None
receipientEmailAddress: str | None
recoveryDate: float | None
recoveryTime: int | None
resDate: float | None
resDueBy: float | None
resPercentSla: int | None
resSla: PicklistIRI | None
resolution: str | None
resolveddate: float | None
respSLApausedon: float | None
responseDate: float | None
responseSLAResumeDate: float | None
senderDomain: str | None
senderEmailAddress: str | None
severity: PicklistIRI | None
siemQuery: str | None
slaPercentage: int | None
slaState: PicklistIRI | None
source: str | None
sourceIP: str | None
sourceId: str | None
sourcedata: str | None
state: PicklistIRI | None
status: PicklistIRI | None
targetAsset: str | None
tasks: list[Any] | None
ticketID: str | None
volatileData: str | None
vulnerabilities: list[Any] | None
warrooms: list[Any] | None
wasPersonalDataAffected: PicklistIRI | None
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
alerts: list[Any] | None
approvalhost: str | None
assets: list[Any] | None
assignedOnDate: float | None
assignedToPerson: RecordIRI | User | None
attachments: list[Any] | None
cVEs: list[Any] | None
comments: list[Any] | None
companies: list[Any] | None
completedOnDate: float | None
description: str | None
dueBy: float | None
hunt: list[Any] | None
incidents: list[Any] | None
indicators: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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}

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
expected: str | None
field: str | None
message: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

valid_options: list[Any] | None
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]
invalid: list[str]
missing: list[str]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

unknown: list[str]
valid: bool
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
config: dict[str, Any]
config_id: str | None
connector: int | None
default: bool
health_status: dict[str, Any]
id: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
remote_status: dict[str, Any]
status: int | None
teams: list[Any]
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
default: bool
id: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
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
category: str | list[str] | None
config_schema: ConfigSchema
configuration: Any
cs_approved: bool | None
cs_compatible: bool | None
description: str | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
operations: list[Operation]
publisher: str | None
version: str | None
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
message: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
backup: str | None
configs_after: int
configs_before: int
from_version: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

to: str | None
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
message: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

property ok: bool

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 dataok reflects status, not data.

operation: str | None
status: str | None
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
errorMessage: str | None
file: RecordIRI | dict[str, Any] | None
fileName: str | None
id: int | None
id_iri: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

progressPercent: int | None
record_type: str | None
status: str | None
type: str | None
uuid: str | None
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
http_status: int | None
message: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
ok: bool | None
request_id: str | None
status: str | None
version: str | None
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
file: RecordIRI | dict[str, Any] | None
id: int | None
id_iri: str | None
jobUuid: str | None
logMessages: list[LogMessage]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

options: dict[str, Any] | list
record_type: str | None
status: str | None
uuid: str | None
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
connector: dict[str, Any]
created_by: str | None
description: str | None
id: int | None
metadata: dict[str, Any]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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
fetch: Workflow | None
ingest: Workflow | None
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}

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

update: Workflow | None
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
collection_name: str | None
collection_uuid: str | None
config_id: str | None
config_name: str | None
connector: str | None
dry_run: bool
existed: bool

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
ingest_playbook_iri: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

playbooks: list[Workflow]
schedule_id: str | None
schedule_name: str | None
scheduled: bool
version: str | None
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

True when the per-configuration ingestion collection exists.

config_id: str | None
property configured: bool

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
metadata: IngestionMetadata | None

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}

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

playbooks: IngestionPlaybooks

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

schedule_enabled: bool | None

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

schedule_id: str | None
schedule_name: str | None
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
collection_uuid: str | None
config_id: str | None
connector: str | None
dry_run: bool
metadata_deleted: int
metadata_ids: list[int]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

schedule_deleted: bool
schedule_name: str | None
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
errorMessage: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

progressPercent: int | None
status: str | None
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
agent: str | None
config_count: int | None
configurations: list[ConnectorConfigSummary]
contributor: str | None
created: str | None
development: bool | None
id: int | None
ingestion_supported: bool | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modified: str | None
name: str | None
publisher: str | None
rpm_installed: bool | None
status: str | None
system: bool | None
tags: list[Any]
version: str | None
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]
property has_next: bool

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

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

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

nextPage: int | None
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
status: str | None
totalItems: int | None
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
message: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
category: str | None
description: str | None
enabled: bool
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

operation: str | None
output_schema: Any
parameters: list[OperationParam]
title: str | None
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
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
apiOperation: str | None
description: str | None
editable: bool
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
onchange: dict[str, list[OperationParam]]
options: list[Any]
placeholder: str | None
required: bool
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
tooltip: str | None
type: str | None
value: Any
visible: bool
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}

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

title: str | None
value: Any

Playbooks & runs

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

Bases: _RequestModel

Typed body for approval().

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

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
is_active: bool
model_config = {'extra': 'allow'}

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

name: str
origin: str | None
priority: str | None
remote_executable: bool
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'}

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

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

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

workflow: str
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
create_date: float | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modify_date: float | None
note: str | None
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

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

snapshot_json: str | None
uuid: str | None
workflow: Any | None
property workflow_iri: str | None

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
input: Any
manual_input_id: int
model_config = {'extra': 'forbid'}

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

step_id: str | None
step_iri: str | None
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]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
status: str | None
steps: dict[str, RunStep]
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
failing_step: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

pk: str | None
status: str | None
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]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
pk: str | None
status: str | None
steps: list[RunStepSnapshot]
task_id: str | None
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]
failure: RunFailure | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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

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

True when status == "finished".

task_id: str | None
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
end_time: str | None
property is_slow: bool

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}

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

result: Any | None
slow_threshold_ms: int

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
status: str | None
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
end_time: str | None
property is_slow: bool

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}

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

name: str | None
result_preview: str | None
slow_threshold_ms: int

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
status: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modified: str | None
name: str | None
pk: str | None
source: str | None
status: str | None
task_id: str | None
uuid: str | None
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]
model_config = {'extra': 'forbid'}

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

module: str
playbook_uuid: str | None
record_uuid: str
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]
inputs: dict[str, Any] | None
model_config = {'extra': 'forbid'}

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

records: list[str]
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}

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

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

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]
changed: list[VersionStepDelta]
groups_added: list[str]
groups_removed: list[str]
property is_clean: bool

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}

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

removed: list[str]
routes_added: list[str]
routes_removed: list[str]
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
from_value: Any | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

step: str | None
to_value: Any | None
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
is_terminal: bool
model_config = {'arbitrary_types_allowed': True}

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

poll_count: int
tree: RunNode

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
button_class: str
button_icon: str
button_text: str
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
collection: bool
data_source: dict[str, Any] | list | None
data_source_filters: dict[str, Any] | list | None
default_value: Any
descriptions: dict[str, str] | None
display_name: str | None
encrypted: bool
form_type: str | None
grid_column: bool
html_escape: bool
id_iri: str | None
identifier: bool | None
imported_by: list[Any]
inverted_field: str | None
property is_picklist: bool

True when the field is a picklist or multiselect picklist.

property is_relationship: bool

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

property label: str | None

Human-readable label from descriptions.singular or displayName.

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

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

name: str | None
order_index: int | None
orphan_removal: bool | None
owns_relationship: bool | None
peer_replicable: bool
readable: bool
recommend: bool
record_type: str | None
sattrib: RecordIRI | dict[str, Any] | None
searchable: bool
skip_serialization: bool
system: bool
tooltip: str | None
type: str | None
unique: bool
uuid: str | None
validation: AttributeValidation | dict[str, Any] | None
visibility: bool | dict[str, Any]
writeable: bool
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
maxlength: int | None
minlength: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

required: bool
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
field: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

module: str | None
problem: str | None
uuid: str | None
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}

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

plural: str | None
singular: str | None
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
archival_criteria: dict[str, Any] | None
archival_filters: list[Any]
attributes: list[AttributeMetadata | dict[str, Any]]
default_sort: list[DefaultSortEntry | dict[str, Any]]
descriptions: ModuleDescriptions | dict[str, str] | None
display_name: str | None
enable_replication: bool
get_attribute(name: str) AttributeMetadata | dict[str, Any] | None[source]

Return the attribute with name, or None.

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

Human-readable singular label from descriptions.

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

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

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

Human-readable plural label from descriptions.

queueable: bool
record_type: str | None
replication_filters: list[Any]
soft_deleteable: bool
system: bool
table_name: str | None
taggable: bool
trackable: bool
type: str | None
unique_constraint: list[dict[str, Any]]
user_ownable: bool
uuid: str | None
writable: bool
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

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

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

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

module: str | None
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}

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}

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
configurations: bool
include: bool
label: str | None
model_config = {'extra': 'allow'}

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

recordCount: int
rpm: bool
value: str
version: str | None
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
model_config = {'extra': 'allow'}

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

value: str
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
includeGlobalVariables: bool
includeSchedules: bool
includeVersions: bool
label: str | None
model_config = {'extra': 'allow'}

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

recordCount: int
value: str
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
includeCorrelations: bool
label: str | None
model_config = {'extra': 'allow'}

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

query: dict[str, Any]
type: str
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]
model_config = {'extra': 'allow'}

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

module: str
uuid: str
viewOptions: str | None

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}

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}

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/usersApiKeyUser). 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
createDate: float | None
createUser: str | dict[str, Any] | None
id: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
recordTags: list[Any] | None
roles: list[str] | None
teams: list[str] | None
title: str | None
userId: str | None
userType: Any | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modify_date: int | None
retrievable: bool | None
status: str | None
time_remaining: int | None
valid_until: int | None
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
api_key: ApiKeyMaterial | None
bind_name: str | None
domain: str | None
is_logged_in: bool | None
loginid: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

status: int | None
tenant: Any | None
user_type: int | None
uuid: str | None
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
createDate: float | None
createUser: str | dict[str, Any] | None
id: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
title: str | None
userId: str | None
userType: Any | None
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
createDate: float | None
createUser: str | User | None
description: str | None
file: FileRecord | str | None
id: int | str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | User | None
name: str | None
recordTags: list[str] | None
type: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

operation: str | None
title: str | None
visible: bool | None
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
buildNumber: int | None
category: str | None
certified: bool | None
description: str | None
help: str | None
infoPath: str | None
label: str | None
lastUpdated: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
publishedDate: int | None
publisher: str | None
releaseNotes: str | None
type: str | None
version: str | None
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}

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

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
publishedDate: float | None
publisher: str | None
recordTags: Any | None
status: str | None
type: str | None
version: str | None
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
property enforced: bool

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

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

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

remaining_actions: int | None
reset_time: int | None
property used_today: int | None

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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
subject: str | None
visible: bool | None
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
configuration_count: int | None
configurations: bool | None
exists: bool | None
include: bool | None
include_install: bool | None
install_mode: str | None
installer_path: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
rpm: bool | None
rpm_exists: bool | None
rpm_name: str | None
value: str | None
version: str | None
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]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
createUser: str | User | None
id: int | str | None
last_export_date: float | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | User | None
name: str | None
options: ExportOptions | None
type: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

tag: str | None
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
createDate: float | None
createUser: str | dict[str, Any] | None
file: Any | None
filename: str | None
id: int | str | None
metadata: Any | None
mimeType: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

status: str | None
uuid: str | None
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.titlenot 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
assignment_type: str | None
created: str | None
custom_fields: dict[str, Any] | None
external_channel_list: list[Any] | None
id: int | None
inline_channel_list: list[Any] | None
input: ManualInputForm | None
is_approval: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

owner_details: dict[str, Any] | None
owners: list[Any] | None
record: str | None
response_mapping: ResponseMapping | None
step_id: int | None
timeout: Any | None
timeout_details: Any | None
title: str | None
type: str | None
unauthenticated_input: bool | None
workflow: str | int | None
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}

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

schema_: ManualInputSchema | None
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}

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

option: str | None
primary: bool | None
step_iri: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

task_id: str | None
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
inputVariables: list[ManualInputVariable] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

title: str | None
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
defaultValue: Any | None
formType: str | None
jinjaExpressionView: bool | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
options: list[Any] | None
playbookField: bool | None
required: bool | None
templateUrl: str | None
title: str | None
tooltip: str | None
type: str | None
usable: bool | None
useRecordFieldDefault: bool | None
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
canDelete: bool | None
canExecute: bool | None
canRead: bool | None
canUpdate: bool | None
fieldPermissions: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

module: Any | None
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
filters: list[Any] | None
isDefault: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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

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

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

system: bool | None
type: str | None
uuid: str | None
viewOptions: str | None
visible: bool | None
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
created_on: str | None
dismissible: bool | None
entity_id: str | None
entity_type: str | None
event_type: str | None
footer: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

read: bool | None
roles: list[Any] | None
teams: list[Any] | None
user: str | None
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}

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

result: str | None
status: str | None
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
icon: str | None
id_iri: str | None
property iri: str | None

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

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

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}

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

order_index: int | None
property ordinal: int | None

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

uuid: str | None
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
property iri: str | None

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

property items: list[PicklistItem]

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}

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

name: str | None
picklists: list[PicklistItem] | None
system: bool | None
uuid: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

widgets: list[PostInstallWidget] | None
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
autoLaunchTriggered: bool | None
buttonLabel: str | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
version: str | None
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
filters: list[QueryFilter]
limit: int | None
logic: str
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

page: int | None
search: str | None
sort: list[Any] | None
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
filters: list[QueryFilter] | None
logic: str | None

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

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

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

operator: str | None
type: str | None
value: Any | None
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
property category_str: str | None

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

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

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

path: str | None
rpm_full_name: str | None
rpm_name: str | None
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
createDate: float | None
createUser: str | dict[str, Any] | None
displayName: str | None
filterArray: list[Any] | None
importedBy: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
parentTemplateId: str | None
templateType: str | None
type: str | None
updateDate: float | None
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
duplicateOption: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

options: list[ManualInputOption] | None
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
hasTriggerStep: bool | None
hideInLogs: bool | None
metadata: dict[str, Any] | list[Any] | None

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}

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

name: str | None
recordTags: list[str] | None
reusable: bool | None
type: str | None
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
importedBy: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modulePermissions: list[ModulePermission] | None
name: str | None
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}

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
category: str | list[Any] | None
certified: bool | None
dependencies: list[Any] | None
description: str | None
fsrMinCompatibility: str | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
postInstallConfig: PostInstallConfig | None
publisher: str | None
version: str | None
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
property job_id: str | None

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}

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
assignee: str | None
createDate: float | None
createUser: str | dict[str, Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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

name: str | None
query: QueryDefinition | None
resultCacheSeconds: int | None
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

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

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

id_iri: str | None
property iri: str | None

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

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

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

module: str | None
name: str | None
system: bool | None

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

type: str | None

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

uuid: str | None
viewOptions: str | None

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

visible: bool | None

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
children: list[Any] | None
description: str | None
importedBy: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
parents: list[Any] | None
siblings: list[Any] | None
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
avatar: Any | None
companyId: Any | None
createDate: float | None
createUser: str | dict[str, Any] | None
csActive: bool | None
department: str | None
description: str | None
email: str | None
firstname: str | None
id: int | None
lastname: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

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

phoneFax: str | None
phoneHome: str | None
phoneMobile: str | None
phoneWork: str | None
title: str | None
type: Any | None
userId: str | None
userType: Any | None
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}

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
compatibility: list[str] | None
description: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
publisher: str | None
subTitle: str | None
title: str | None
version: str | None
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
collection: str | None
createDate: float | None
createUser: str | dict[str, Any] | None
debug: bool | None
deletedAt: float | None
description: str | None
id: int | None
importedBy: list[Any] | None
isActive: bool | None
isEditable: bool | None
isPrivate: bool | None
lastModifyDate: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
parameters: list[str] | None
playbookOrigin: PicklistIRI | None
priority: PicklistIRI | None
recordTags: list[str] | None
remoteExecutableFlag: bool | None
singleRecordExecution: bool | None
synchronous: bool | None
tag: str | None
triggerLimit: Any | None
triggerStep: str | None
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
createUser: str | dict[str, Any] | None
deletedAt: float | None
description: str | None
id: int | None
image: Any | None
importedBy: list[Any] | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
recordTags: list[str] | None
visible: bool | None
workflows: list[Any] | None
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
debug: bool | None
env: Any | None
metadata: Any | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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

AI & investigations

class pyfsr.models.AgentConfig(*, 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
llm_provider: str | None
masking_agent: str | None
mcp_server: list[str]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

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
agent_version: str | None
config: AgentConfig
config_id: str | None
default: bool
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
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
additional_information: list[dict[str, Any]]
category: str | None
classpath: str | None
config_count: int | None
config_schema: Any | None
configuration: list[Any]
dependencies: list[Any]
description: str | None
id: int | None
inputformat: dict[str, Any]
installed: bool | None
jailbreakguard: bool | None
label: str | None
llmconfig: Any | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
outputformat: dict[str, Any]
piimasking: bool | None
prompt: Any | None
status: str | None
system: bool | None
tags: list[str]
uuid: str | None
version: str | None
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
confidence: str | None
evidence: Any | None
logs: list[dict[str, Any]]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

phases: list[dict[str, Any]]
status: str | None
task_id: str | None
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]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

restricted: list[str]
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}

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

status: str | None
task_id: str | None
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
evidence: str | None
index: int | None
information_type: Any | None
input: Any | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

question: str | None
response: Any | None
status: str | None
supports: list[str]
weakens: list[str]
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]]
logs: list[dict[str, Any]]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

status: str | None
summary: dict[str, Any] | None
task_id: str | None
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
apikey: str | None
baseurl: str | None
config: dict[str, Any]
isdefault: bool | None
model: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modelname: str | None
name: str | None
provider: str | None
uuid: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
uuid: str | None
version: str | None
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
authentication: str | dict[str, Any] | None
command: str | None
description: str | None
metadata: Any | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
timeout: int | None
transport: str | None
type: str | None
url: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
uuid: str | None
valid: bool
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
inputSchema: dict[str, Any] | None
property input_schema: dict[str, Any] | None

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}

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

name: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

property ok: bool

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
status: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

tools: list[MCPTool]
valid: bool
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, **extra_data: Any)[source]

Bases: _Lenient

One MCP/connector tool invocation, from llm_activity_logs – see tool_usage().

correlation_id: str | None
latency_ms: int | None
model: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

server: str | None
server_uuid: str | None
title: str | None
tool_args: Any | None
tool_name: str | None

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
agentId: str | None
configurationHealth: PicklistIRI | None
created: str | None
description: str | None
installerType: PicklistIRI | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modified: str | None
name: str | None
router: RecordIRI | dict[str, Any] | None
uuid: str | None
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
agentId: str | None
errorMessage: str | None
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
progressPercent: int | None
status: str | None
version: str | None

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.

name must match the package’s top-level folder, and agentclass must name a class defined in agent.py; AgentPackage cross-checks both. 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]]
agentclass: str | None
category: str | None
configuration: dict[str, Any]
contributor: str | None
cs_approved: bool | None
cs_compatible: bool | None
description: str | None
fsrMinCompatibility: str | None
help_online: str | None
icon_large_name: str | None
icon_small_name: str | None
inputformat: dict[str, Any]
label: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str
outputformat: dict[str, Any]
publisher: str | None
tags: list[str]
version: str
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]]
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}

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>, 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:

  • 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 of agent.py when known — used to check agentclass and cross-check referenced prompt uuids.

files: list[str]

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
memory: AgentMemory
model_config = {'arbitrary_types_allowed': True}

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

prompts: AgentPromptFile
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. 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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
response_format: Any | None
system_instruction: str | None
user_instruction: str | None
validation_instruction: str | None
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}

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

prompts: dict[str, AgentPrompt]

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
category: str | None
channel_preference_field: Any | None
entity_id: Any | None
entity_type: str | None
event_source: str | None
event_type: str | None
expiry: Any | None
is_active: bool | None
is_system: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
parent_rule: Any | None
priority: int | None
source: Any | None
trigger_condition: dict[str, Any] | None
uuid: str | None
visible: bool | None
workflow: Any | None
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
actionType: dict[str, Any] | str | None
applicableOn: str | None
createDate: float | None
createUser: str | dict[str, Any] | None
criteria: dict[str, Any] | None
description: str | None
endDate: float | None
entityType: str | None
id: int | None
isActive: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modifyDate: float | None
modifyUser: str | dict[str, Any] | None
name: str | None
priority: int | None
recordTags: list[Any] | None
skipPlaybookExecution: Any | None
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
default_params: dict[str, Any] | None
description: str | None
is_active: bool | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
type: str | None
uuid: str | None

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
day_of_week: str | None
hour: str | None
id: int | None
minute: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

month_of_year: str | None
timezone: str | None
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
clocked: Any | None
crontab: CrontabScheduleModel | None
date_changed: str | None
description: str | None
enabled: bool | None
exchange: str | None
expire_seconds: int | None
expires: str | None
headers: str | None
id: str | None
interval: Any | None
kwargs: dict[str, Any] | None
last_run_at: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
one_off: bool | None
priority: Any | None
queue: str | None
routing_key: str | None
solar: Any | None
start_time: str | None
task: str | None
total_run_count: int | None

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
enablePublish: bool | None
id_iri: str | None
installed: bool | None
metadata: dict | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

name: str | None
property published: bool

True once the widget is live – installed and no longer a draft.

subTitle: str | None
title: str | None
uuid: str | None
version: str | None

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
exists: bool | None
icon: str | None
include: bool | None
is_enabled: bool | None
property is_group: bool

True if this entry has children (i.e. is a menu group).

items: list[NavItem] | None
merge_type: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

open: bool | None
open_status: bool | None
require: NavRequire | list[Any] | None
state: NavState | None
title: str | None
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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

module: str | None
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}

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

name: str | None
parameters: dict[str, Any] | list[Any] | None

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

The assignee as a User, or None.

Reads assignedTo (alerts/incidents) and falls back to assignedToPerson (tasks).

property create_user: Actor | None

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
property iri: str | None

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}

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

property modify_user: Actor | None

The modifyUser as a User or Appliance.

See create_user for dispatch logic.

property owner_teams: list[Any]

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
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

_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

True when a playbook made this change (user == "Playbook").

component: str | None
data: dict[str, Any] | None
display_name: str | None
entity_type: str | None
entity_uuid: str | None
id: int | str | None
property linked_entity_display: str | None

The display name of the linked entity, or None.

property linked_entity_iri: str | None

The IRI of the entity linked/unlinked (linkEntityDetails.iri), or None.

property linked_entity_type: str | None

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}

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

operation: str | None
playbook_iri: str | None
playbook_name: str | None
source: str | None
property timestamp_iso: str | None

transaction_date as an ISO-8601 string, or None.

title: str | None
transaction_date: int | None
user: str | None
user_id: str | None
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]
concurrent_changes: list[LifecycleEntry]
concurrent_runs: list[dict[str, Any]]
entity_type: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

property other_playbooks: list[str]

Playbook names (excluding the run itself) that changed the record.

record_iri: str | None
record_uuid: str | None
run_created: str | None
run_modified: str | None
run_name: str | None
run_pk: str | None
run_status: str | None
summary() str[source]

A one-line summary suitable for agent output.

window_seconds: int
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
entity_uuid: str | None
execution_pk: str | None
execution_status: str | None
kind: str | None
linked_entity_display: str | None
linked_entity_iri: str | None
linked_entity_type: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

operation: str | None
playbook_name: str | None
raw: dict[str, Any] | None
property timestamp_iso: str | None

Timestamp as an ISO-8601 string, or None.

timestamp_ms: int | None
title: str | None
user: str | None
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
property by_playbook: list[LifecycleEntry]

Entries caused by a playbook (user == "Playbook" or kind == "execution").

property comments: list[LifecycleEntry]

Audit entries with operation Comment.

entity_type: str | None
entity_uuid: str | None
entries: list[LifecycleEntry]
execution_count: int
property field_changes: list[LifecycleEntry]

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}

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

property playbook_names: list[str]

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]]
kill_chain_phases: list[dict[str, Any]]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

objects: list[StixObject]
type: str
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]
first_seen: str | None
last_seen: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

objectives: str | None
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]]
labels: list[str]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

pattern_type: str | None
valid_from: str | None
valid_until: str | None
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]
first_seen: str | None
goals: list[str]
last_seen: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

primary_motivation: str | None
resource_level: str | None
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
kill_chain_phases: list[dict[str, Any]]
malware_types: list[str]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
description: str | None
id: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

modified: str | None
name: str | None
pattern: str | None
spec_version: str | None
type: str
value: str | None
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}

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

object_refs: list[str]
published: str | None
report_types: list[str]
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]
first_seen: str | None
kill_chain_phases: list[dict[str, Any]]
last_seen: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

threat_actor_types: list[str]
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]]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

tool_types: list[str]
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
external_references: list[dict[str, Any]]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

objects_processed: int | None
property ok: bool

True iff the server reported status == "success".

status: str | None
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
can_write: bool | None
description: str | None
id: str | None
media_types: list[str]
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

title: str | None
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
description: str | None
max_content_length: int | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

title: str | None
versions: list[str]
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}

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

objects: list[TaxiiManifestEntry]
total_items: int | None
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
id: str | None
media_type: str | None
model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

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

version: str | None
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}

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

objects: list[StixObject]
total_items: int | None