{
  "components": {
    "schemas": {
      "AttributedSignal": {
        "description": "A signal and the analyzer that emitted it.",
        "properties": {
          "analyzer_id": {
            "title": "Analyzer Id",
            "type": "string"
          },
          "evidence": {
            "default": "",
            "description": "Short human-readable proof",
            "title": "Evidence",
            "type": "string"
          },
          "location": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "\"header:From\", \"url:3\", \"part:1.2\"",
            "title": "Location"
          },
          "name": {
            "description": "e.g. auth.dmarc_fail",
            "pattern": "^[a-z0-9_]+(\\.[a-z0-9_]+)+$",
            "title": "Name",
            "type": "string"
          },
          "weight": {
            "description": "Contribution, in score points",
            "maximum": 100,
            "minimum": -100,
            "title": "Weight",
            "type": "integer"
          }
        },
        "required": [
          "name",
          "weight",
          "analyzer_id"
        ],
        "title": "AttributedSignal",
        "type": "object"
      },
      "Egress": {
        "description": "What data a plugin sends outside SPOT; an image without the label counts as content.",
        "enum": [
          "none",
          "indicators",
          "content"
        ],
        "title": "Egress",
        "type": "string"
      },
      "ErrorAnswer": {
        "description": "An error the app answers itself: 401 without the plugin's own token, 500\nwhen the handler fails. It never holds the request's content.",
        "properties": {
          "detail": {
            "description": "\"unauthorized\", \"analysis failed\", \"response failed\"",
            "title": "Detail",
            "type": "string"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorAnswer",
        "type": "object"
      },
      "HealthAnswer": {
        "description": "``GET /health``'s body: the plugin's state and the version of its\nmanifest. ``reason`` is left out when there is none.",
        "properties": {
          "reason": {
            "anyOf": [
              {
                "maxLength": 200,
                "pattern": "^[^\\x00-\\x1f\\x7f]*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Why, in a few words; no content, no control character",
            "title": "Reason"
          },
          "status": {
            "enum": [
              "healthy",
              "starting",
              "unhealthy"
            ],
            "title": "Status",
            "type": "string"
          },
          "version": {
            "description": "The manifest's version",
            "title": "Version",
            "type": "string"
          }
        },
        "required": [
          "status",
          "version"
        ],
        "title": "HealthAnswer",
        "type": "object"
      },
      "InvalidRequestAnswer": {
        "description": "The 422 for a request body that does not validate: the fields refused.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/RefusedField"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "InvalidRequestAnswer",
        "type": "object"
      },
      "Manifest": {
        "description": "A plugin's declaration of what it is, what it needs and what leaves SPOT.",
        "properties": {
          "capabilities": {
            "items": {
              "type": "string"
            },
            "title": "Capabilities",
            "type": "array"
          },
          "compatibility": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "SPOT version range, e.g. >=2.0.0,<3.0.0",
            "title": "Compatibility"
          },
          "contract": {
            "default": 2,
            "title": "Contract",
            "type": "integer"
          },
          "egress": {
            "$ref": "#/components/schemas/Egress",
            "default": "content"
          },
          "healthcheck": {
            "default": "/health",
            "title": "Healthcheck",
            "type": "string"
          },
          "id": {
            "title": "Id",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/PluginKind"
          },
          "port": {
            "default": 8000,
            "title": "Port",
            "type": "integer"
          },
          "requires_external_ingress": {
            "default": false,
            "title": "Requires External Ingress",
            "type": "boolean"
          },
          "resources": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Resources"
              },
              {
                "type": "null"
              }
            ]
          },
          "settings": {
            "additionalProperties": {
              "$ref": "#/components/schemas/SettingField"
            },
            "title": "Settings",
            "type": "object"
          },
          "version": {
            "title": "Version",
            "type": "string"
          }
        },
        "required": [
          "id",
          "version",
          "kind"
        ],
        "title": "Manifest",
        "type": "object"
      },
      "PluginKind": {
        "description": "What a plugin does; a plugin has exactly one kind.",
        "enum": [
          "analyzer",
          "context_provider",
          "mail_retriever",
          "responder"
        ],
        "title": "PluginKind",
        "type": "string"
      },
      "RefusedField": {
        "description": "A field of a request body that does not validate: never the value sent.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "loc",
          "msg"
        ],
        "title": "RefusedField",
        "type": "object"
      },
      "Resources": {
        "description": "Memory and CPU a plugin container may use.",
        "properties": {
          "cpus": {
            "default": 1.0,
            "description": "CPU cores",
            "exclusiveMinimum": 0.0,
            "maximum": 64.0,
            "title": "Cpus",
            "type": "number"
          },
          "memory_mb": {
            "default": 1024,
            "description": "Memory in MB",
            "minimum": 128.0,
            "title": "Memory Mb",
            "type": "integer"
          }
        },
        "title": "Resources",
        "type": "object"
      },
      "RespondRequest": {
        "description": "The body of ``POST {responder}/internal/respond``.",
        "properties": {
          "decided_by": {
            "title": "Decided By",
            "type": "string"
          },
          "email_id": {
            "format": "uuid",
            "title": "Email Id",
            "type": "string"
          },
          "job_id": {
            "title": "Job Id",
            "type": "string"
          },
          "mode": {
            "description": "The job's mode (its connection's and its route's); a responder acts only in enforce",
            "enum": [
              "observe",
              "enforce"
            ],
            "title": "Mode",
            "type": "string"
          },
          "score": {
            "maximum": 100,
            "minimum": 0,
            "title": "Score",
            "type": "integer"
          },
          "signals": {
            "items": {
              "$ref": "#/components/schemas/AttributedSignal"
            },
            "title": "Signals",
            "type": "array"
          },
          "verdict": {
            "$ref": "#/components/schemas/VerdictClass"
          },
          "workflow_id": {
            "title": "Workflow Id",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "email_id",
          "workflow_id",
          "verdict",
          "score",
          "decided_by",
          "signals",
          "mode"
        ],
        "title": "RespondRequest",
        "type": "object"
      },
      "SettingField": {
        "description": "One setting of a plugin, as ``spot.plugin.settings`` declares it.\n\nAn ``enum`` that no value can satisfy is refused, so that the plugin\nfails when its manifest is built rather than when an admin configures it:\nan empty one, or one whose values do not fit ``type`` (``integer``: int,\nnever bool; ``number``: int or float, never bool; ``boolean``: bool;\n``string``, ``url`` and ``secret``: str).\n\n``multiline`` marks a value of several lines, such as a PEM certificate:\nthe dashboard renders a textarea and keeps the line breaks. It is for\n``string`` and ``secret`` settings only; on any other type it is refused\nin the same way.\n\n``minimum`` and ``maximum`` bound an ``integer`` (by ints) or a ``number``\nsetting, and ``pattern`` a ``string`` setting, so that core refuses a\nvalue out of them when it is saved. Refused in the same way: a bound on\nanother type, a minimum above the maximum, a pattern that does not\ncompile, a default or an ``enum`` value out of them, and a ``number``'s\ndefault or ``enum`` value that is not finite (NaN, infinity).",
        "properties": {
          "default": {
            "title": "Default"
          },
          "description": {
            "default": "",
            "title": "Description",
            "type": "string"
          },
          "enum": {
            "anyOf": [
              {
                "items": {},
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Allowed values, each of the setting's type",
            "title": "Enum"
          },
          "maximum": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "The largest value allowed; integer and number only",
            "title": "Maximum"
          },
          "minimum": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "The smallest value allowed; integer and number only",
            "title": "Minimum"
          },
          "multiline": {
            "default": false,
            "description": "The value has several lines (a PEM certificate); string and secret only",
            "title": "Multiline",
            "type": "boolean"
          },
          "pattern": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "A regular expression the whole value matches, as Python's re.fullmatch reads it; string only. Core checks it when a value is saved; the dashboard shows it as a hint, as browsers read regular expressions otherwise",
            "title": "Pattern"
          },
          "required": {
            "default": false,
            "title": "Required",
            "type": "boolean"
          },
          "secret": {
            "default": false,
            "title": "Secret",
            "type": "boolean"
          },
          "type": {
            "$ref": "#/components/schemas/SettingType"
          }
        },
        "required": [
          "type"
        ],
        "title": "SettingField",
        "type": "object"
      },
      "SettingType": {
        "description": "Types of a plugin setting.",
        "enum": [
          "string",
          "integer",
          "number",
          "boolean",
          "url",
          "secret"
        ],
        "title": "SettingType",
        "type": "string"
      },
      "VerdictClass": {
        "description": "The class of an email, for one analyzer or for the workflow.",
        "enum": [
          "malicious",
          "suspicious",
          "spam",
          "benign",
          "unknown"
        ],
        "title": "VerdictClass",
        "type": "string"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "description": "The plugin's own token",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "The routes a plugin of kind responder serves, as spot-sdk-python 2.1.1's create_plugin_app declares them.",
    "title": "SPOT plugin: responder",
    "version": "2.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/health": {
      "get": {
        "operationId": "health_route_health_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthAnswer"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Health Route"
      }
    },
    "/internal/respond": {
      "post": {
        "operationId": "respond_route_internal_respond_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RespondRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAnswer"
                }
              }
            },
            "description": "Without the plugin's own token"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvalidRequestAnswer"
                }
              }
            },
            "description": "The body does not validate"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAnswer"
                }
              }
            },
            "description": "The handler failed"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Respond Route"
      }
    },
    "/manifest": {
      "get": {
        "operationId": "get_manifest_manifest_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Manifest"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Get Manifest"
      }
    }
  }
}
