{
  "openapi": "3.1.1",
  "info": {
    "title": "Batmail API",
    "summary": "The public, no-signup email tools behind batmail.ai.",
    "description": "Batmail is an AI layer over Gmail: it works overnight, runs rules written in plain English, and sends one morning roundup of the mail that needs a person.\n\nThis document describes the part of it that is open to everyone: six email tools that take a JSON body and return a JSON result. No account, no key, no cookie.\n\n## Errors\n\nEvery failure returns the same JSON envelope, never an HTML page:\n\n```json\n{ \"ok\": false, \"error\": { \"code\": \"rate_limited\", \"message\": \"...\", \"hint\": \"...\", \"status\": 429, \"docs\": \"https://www.batmail.ai/openapi.json\" } }\n```\n\n`code` is stable and safe to branch on. `hint` says what to change before retrying.\n\n## Keys and scopes\n\nThese endpoints need no credential. An agent that would rather be identified can send an API key, and that key is then held to its scopes: a key issued for one tool cannot run the other five. Scopes are listed under `x-scopes`, and the scopes that satisfy each endpoint under `x-required-scopes`.\n\n## Rate limits\n\nPer caller in a short burst window, per caller per day, and across every caller per day. A refusal is a `rate_limited` error carrying `retry-after` in seconds. An authenticated caller is metered by key id rather than by address.\n\n## Sending mail through Batmail\n\nThere is no endpoint for that. Batmail acts on a Gmail account only after its owner connects it over Google OAuth, and only inside the app.",
    "version": "1.0.0",
    "termsOfService": "https://www.batmail.ai/terms",
    "contact": {
      "name": "Batmail support",
      "email": "support@batmail.ai",
      "url": "https://www.batmail.ai/tools"
    }
  },
  "servers": [
    {
      "url": "https://www.batmail.ai",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Site index for agents, in llms.txt format.",
    "url": "https://www.batmail.ai/llms.txt"
  },
  "tags": [
    {
      "name": "Free tools",
      "description": "Public email tools. No signup, no key, rate limited."
    }
  ],
  "x-scopes": [
    {
      "name": "tools:run",
      "summary": "Run any of the free email tools under /api/tools."
    },
    {
      "name": "tools:out-of-office-generator:run",
      "summary": "Run only the Out of Office Generator (POST /api/tools/out-of-office-generator)."
    },
    {
      "name": "tools:phishing-email-checker:run",
      "summary": "Run only the Phishing Email Checker (POST /api/tools/phishing-email-checker)."
    },
    {
      "name": "tools:gmail-filter-generator:run",
      "summary": "Run only the Gmail Filter Generator (POST /api/tools/gmail-filter-generator)."
    },
    {
      "name": "tools:email-header-analyzer:run",
      "summary": "Run only the Email Header Analyzer (POST /api/tools/email-header-analyzer)."
    },
    {
      "name": "tools:professional-email-rewriter:run",
      "summary": "Run only the Professional Email Rewriter (POST /api/tools/professional-email-rewriter)."
    },
    {
      "name": "tools:subject-line-tester:run",
      "summary": "Run only the Subject Line Tester (POST /api/tools/subject-line-tester)."
    }
  ],
  "paths": {
    "/api/tools/out-of-office-generator": {
      "post": {
        "operationId": "run-out-of-office-generator",
        "summary": "Out of Office Generator",
        "tags": [
          "Free tools"
        ],
        "description": "Say when you are away and what should happen. Get a clean auto-reply you can paste into Gmail or Outlook.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:out-of-office-generator:run`.\nRuns a model, so it is rate limited per caller and per day across all callers.\nThe human page for this tool is https://www.batmail.ai/tools/out-of-office-generator.",
        "x-required-scopes": [
          "tools:run",
          "tools:out-of-office-generator:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "context": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 1200,
                    "description": "When you are away and anything the message should mention, in plain words."
                  },
                  "returnDate": {
                    "description": "The date you are back, written how you would write it to a colleague.",
                    "type": "string",
                    "maxLength": 80
                  },
                  "backupContact": {
                    "description": "Who to contact while you are away, if anyone.",
                    "type": "string",
                    "maxLength": 160
                  },
                  "tone": {
                    "default": "professional",
                    "description": "How the message should read.",
                    "type": "string",
                    "enum": [
                      "professional",
                      "friendly",
                      "brief",
                      "formal"
                    ]
                  },
                  "checkingOccasionally": {
                    "default": false,
                    "description": "True when you will check mail occasionally rather than not at all.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "context"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "Out of office messages, one per angle, plus what to check before sending.",
                      "properties": {
                        "variants": {
                          "type": "array",
                          "description": "Two or three complete messages. The first is the recommended one.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "label": {
                                "type": "string",
                                "description": "What makes this version different."
                              },
                              "subject": {
                                "type": "string",
                                "description": "Auto-reply subject line."
                              },
                              "body": {
                                "type": "string",
                                "description": "The message body, ready to paste."
                              }
                            },
                            "required": [
                              "label",
                              "subject",
                              "body"
                            ]
                          }
                        },
                        "notes": {
                          "type": "array",
                          "description": "Things to check before switching the auto-reply on.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "variants",
                        "notes"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/phishing-email-checker": {
      "post": {
        "operationId": "run-phishing-email-checker",
        "summary": "Phishing Email Checker",
        "tags": [
          "Free tools"
        ],
        "description": "Paste a suspicious email. Get a verdict, the exact red flags found in it, and what to do next.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:phishing-email-checker:run`.\nRuns a model, so it is rate limited per caller and per day across all callers.\nThe human page for this tool is https://www.batmail.ai/tools/phishing-email-checker.",
        "x-required-scopes": [
          "tools:run",
          "tools:phishing-email-checker:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emailText": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 12000,
                    "description": "The whole suspect message as received: sender, subject and body. An address or link on its own is rejected."
                  }
                },
                "required": [
                  "emailText"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "A verdict on one pasted email, with the evidence behind it.",
                      "properties": {
                        "verdict": {
                          "type": "string",
                          "enum": [
                            "likely-phishing",
                            "suspicious",
                            "probably-safe",
                            "not-enough-info"
                          ],
                          "description": "The overall read. `not-enough-info` means the paste was too thin to judge."
                        },
                        "riskScore": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100,
                          "description": "0 safe to 100 hostile."
                        },
                        "summary": {
                          "type": "string",
                          "description": "One paragraph on what this message is."
                        },
                        "redFlags": {
                          "type": "array",
                          "description": "Signals found in the message, strongest first.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "signal": {
                                "type": "string",
                                "description": "The signal, named."
                              },
                              "detail": {
                                "type": "string",
                                "description": "Where it appears in this message."
                              },
                              "severity": {
                                "type": "string",
                                "enum": [
                                  "high",
                                  "medium",
                                  "low"
                                ]
                              }
                            },
                            "required": [
                              "signal",
                              "detail",
                              "severity"
                            ]
                          }
                        },
                        "reassuring": {
                          "type": "array",
                          "description": "Signals that point the other way. May be empty.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "nextSteps": {
                          "type": "array",
                          "description": "What the reader should do now.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "verdict",
                        "riskScore",
                        "summary",
                        "redFlags",
                        "reassuring",
                        "nextSteps"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/gmail-filter-generator": {
      "post": {
        "operationId": "run-gmail-filter-generator",
        "summary": "Gmail Filter Generator",
        "tags": [
          "Free tools"
        ],
        "description": "Describe the mail you want handled in plain English. Get the exact Gmail search syntax and the steps to save it as a filter.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:gmail-filter-generator:run`.\nRuns a model, so it is rate limited per caller and per day across all callers.\nThe human page for this tool is https://www.batmail.ai/tools/gmail-filter-generator.",
        "x-required-scopes": [
          "tools:run",
          "tools:gmail-filter-generator:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 1000,
                    "description": "The mail you want caught, described in plain words."
                  },
                  "action": {
                    "description": "What Gmail should do with it (archive, label, star, forward).",
                    "type": "string",
                    "maxLength": 300
                  }
                },
                "required": [
                  "description"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "A Gmail search query and the filter to build from it.",
                      "properties": {
                        "query": {
                          "type": "string",
                          "description": "The Gmail search query, ready to paste."
                        },
                        "explanation": {
                          "type": "string",
                          "description": "What the query matches, in plain words."
                        },
                        "operators": {
                          "type": "array",
                          "description": "Each operator used in the query, explained.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "operator": {
                                "type": "string",
                                "description": "e.g. `from:`, `has:attachment`."
                              },
                              "meaning": {
                                "type": "string",
                                "description": "What it does in this query."
                              }
                            },
                            "required": [
                              "operator",
                              "meaning"
                            ]
                          }
                        },
                        "filterActions": {
                          "type": "array",
                          "description": "Gmail filter actions to tick for this filter.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "steps": {
                          "type": "array",
                          "description": "How to create the filter in Gmail, in order.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "caveats": {
                          "type": "array",
                          "description": "What this filter will and will not catch.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "query",
                        "explanation",
                        "operators",
                        "filterActions",
                        "steps",
                        "caveats"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/email-header-analyzer": {
      "post": {
        "operationId": "run-email-header-analyzer",
        "summary": "Email Header Analyzer",
        "tags": [
          "Free tools"
        ],
        "description": "Paste raw headers. See SPF, DKIM and DMARC results, every hop the message took, and where it was delayed.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:email-header-analyzer:run`.\nParses the input in process. No model call, and nothing leaves the server.\nThe human page for this tool is https://www.batmail.ai/tools/email-header-analyzer.",
        "x-required-scopes": [
          "tools:run",
          "tools:email-header-analyzer:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "headers": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 20000,
                    "description": "The raw header block, copied from 'Show original' in Gmail or its equivalent."
                  }
                },
                "required": [
                  "headers"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "A parsed email header block: authentication, delivery path and findings.",
                      "properties": {
                        "summary": {
                          "type": "array",
                          "description": "The headline facts (from, to, subject, message id) as label/value pairs.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "label": {
                                "type": "string"
                              },
                              "value": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "label",
                              "value"
                            ]
                          }
                        },
                        "auth": {
                          "type": "array",
                          "description": "SPF, DKIM and DMARC results as found in the headers.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "SPF, DKIM or DMARC."
                              },
                              "state": {
                                "type": "string",
                                "enum": [
                                  "pass",
                                  "fail",
                                  "softfail",
                                  "neutral",
                                  "none",
                                  "unknown"
                                ]
                              },
                              "meaning": {
                                "type": "string",
                                "description": "What that state means for this message."
                              }
                            },
                            "required": [
                              "name",
                              "state",
                              "meaning"
                            ]
                          }
                        },
                        "hops": {
                          "type": "array",
                          "description": "The delivery path, oldest hop first.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "index": {
                                "type": "integer",
                                "description": "Position in the delivery path, starting at 1 for the oldest hop."
                              },
                              "from": {
                                "type": "string"
                              },
                              "by": {
                                "type": "string"
                              },
                              "with": {
                                "type": "string",
                                "description": "Protocol the hop used."
                              },
                              "ip": {
                                "type": "string"
                              },
                              "timestamp": {
                                "type": "string",
                                "description": "As written in the header."
                              },
                              "delaySeconds": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Seconds spent at this hop, or null when it cannot be computed."
                              }
                            },
                            "required": [
                              "index",
                              "from",
                              "by",
                              "with",
                              "ip",
                              "timestamp",
                              "delaySeconds"
                            ]
                          }
                        },
                        "totalDelaySeconds": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "End to end delivery time, or null when the timestamps do not allow it."
                        },
                        "slowestHopIndex": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The `index` of the slowest hop, or null when nothing stands out."
                        },
                        "findings": {
                          "type": "array",
                          "description": "What is worth knowing about this message, most serious first.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "severity": {
                                "type": "string",
                                "enum": [
                                  "high",
                                  "medium",
                                  "info"
                                ]
                              },
                              "title": {
                                "type": "string"
                              },
                              "detail": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "severity",
                              "title",
                              "detail"
                            ]
                          }
                        },
                        "headerNames": {
                          "type": "array",
                          "description": "Every header name found in the pasted block, in order.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "summary",
                        "auth",
                        "hops",
                        "totalDelaySeconds",
                        "slowestHopIndex",
                        "findings",
                        "headerNames"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/professional-email-rewriter": {
      "post": {
        "operationId": "run-professional-email-rewriter",
        "summary": "Professional Email Rewriter",
        "tags": [
          "Free tools"
        ],
        "description": "Paste a rough draft and pick a tone. Get a clear, professional version, plus what changed and why.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:professional-email-rewriter:run`.\nRuns a model, so it is rate limited per caller and per day across all callers.\nThe human page for this tool is https://www.batmail.ai/tools/professional-email-rewriter.",
        "x-required-scopes": [
          "tools:run",
          "tools:professional-email-rewriter:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "draft": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 6000,
                    "description": "The email draft to rewrite."
                  },
                  "tone": {
                    "default": "professional",
                    "description": "How the rewrite should read.",
                    "type": "string",
                    "enum": [
                      "professional",
                      "friendly",
                      "direct",
                      "formal",
                      "apologetic"
                    ]
                  },
                  "recipient": {
                    "description": "Who it is going to, if that changes how it should read.",
                    "type": "string",
                    "maxLength": 160
                  }
                },
                "required": [
                  "draft"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "The rewritten email and what changed.",
                      "properties": {
                        "subject": {
                          "type": "string",
                          "description": "Suggested subject line."
                        },
                        "body": {
                          "type": "string",
                          "description": "The rewritten email."
                        },
                        "originalTone": {
                          "type": "string",
                          "description": "How the draft read before the rewrite."
                        },
                        "changes": {
                          "type": "array",
                          "description": "Each edit, with the reason for it.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "change": {
                                "type": "string",
                                "description": "What was changed."
                              },
                              "why": {
                                "type": "string",
                                "description": "Why it reads better."
                              }
                            },
                            "required": [
                              "change",
                              "why"
                            ]
                          }
                        },
                        "openItems": {
                          "type": "array",
                          "description": "Anything the draft left unanswered that the sender must fill in.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "subject",
                        "body",
                        "originalTone",
                        "changes",
                        "openItems"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/subject-line-tester": {
      "post": {
        "operationId": "run-subject-line-tester",
        "summary": "Subject Line Tester",
        "tags": [
          "Free tools"
        ],
        "description": "Test a subject line before you send it. Get an open-rate read, spam-trigger check, mobile preview and stronger rewrites.\n\nPublic: no key is required. A caller that does send an API key must hold `tools:run` or `tools:subject-line-tester:run`.\nRuns a model, so it is rate limited per caller and per day across all callers.\nThe human page for this tool is https://www.batmail.ai/tools/subject-line-tester.",
        "x-required-scopes": [
          "tools:run",
          "tools:subject-line-tester:run"
        ],
        "x-scope-optional": true,
        "security": [
          {},
          {
            "bearerApiKey": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, at most 24576 bytes.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "subject": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 300,
                    "description": "The subject line to score."
                  },
                  "kind": {
                    "default": "cold-outreach",
                    "description": "What kind of email it is; each type is graded by different rules.",
                    "type": "string",
                    "enum": [
                      "cold-outreach",
                      "newsletter",
                      "job-application",
                      "internal",
                      "follow-up",
                      "sales"
                    ]
                  }
                },
                "required": [
                  "subject"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "description": "A score for one subject line, the exact measurements, and stronger rewrites.",
                      "properties": {
                        "score": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100,
                          "description": "0 weak to 100 strong."
                        },
                        "verdict": {
                          "type": "string",
                          "description": "One sentence on how it lands."
                        },
                        "metrics": {
                          "type": "object",
                          "description": "Counted in code, never by the model, so the numbers are exact.",
                          "properties": {
                            "characters": {
                              "type": "integer"
                            },
                            "words": {
                              "type": "integer"
                            },
                            "mobilePreview": {
                              "type": "string",
                              "description": "What a phone shows before it cuts."
                            },
                            "mobileTruncated": {
                              "type": "boolean"
                            },
                            "desktopPreview": {
                              "type": "string",
                              "description": "What a desktop list shows."
                            },
                            "desktopTruncated": {
                              "type": "boolean"
                            },
                            "shoutedWords": {
                              "type": "integer",
                              "description": "ALL-CAPS words of two or more letters."
                            },
                            "exclamationMarks": {
                              "type": "integer"
                            },
                            "hasEmoji": {
                              "type": "boolean"
                            },
                            "lengthIsIdeal": {
                              "type": "boolean",
                              "description": "True between 30 and 50 characters."
                            }
                          },
                          "required": [
                            "characters",
                            "words",
                            "mobilePreview",
                            "mobileTruncated",
                            "desktopPreview",
                            "desktopTruncated",
                            "shoutedWords",
                            "exclamationMarks",
                            "hasEmoji",
                            "lengthIsIdeal"
                          ]
                        },
                        "strengths": {
                          "type": "array",
                          "description": "What genuinely works. May be empty for a weak line.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "issues": {
                          "type": "array",
                          "description": "Specific problems, strongest first.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "issue": {
                                "type": "string"
                              },
                              "fix": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "issue",
                              "fix"
                            ]
                          }
                        },
                        "spamTriggers": {
                          "type": "array",
                          "description": "Words or patterns present in this line. May be empty.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "rewrites": {
                          "type": "array",
                          "description": "Three stronger alternatives, each a different angle.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "line": {
                                "type": "string"
                              },
                              "why": {
                                "type": "string",
                                "description": "Why this version is stronger."
                              },
                              "characters": {
                                "type": "integer",
                                "description": "Counted in code."
                              }
                            },
                            "required": [
                              "line",
                              "why",
                              "characters"
                            ]
                          }
                        }
                      },
                      "required": [
                        "score",
                        "verdict",
                        "metrics",
                        "strengths",
                        "issues",
                        "spamTriggers",
                        "rewrites"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "result"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body was missing, unreadable, or failed validation. (code `invalid_request`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_request",
                    "message": "The body was missing, unreadable, or failed validation.",
                    "hint": "Send a JSON body matching the requestBody schema in /openapi.json.",
                    "status": 400,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "401": {
            "description": "An API key was presented and not recognised. (code `invalid_token`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "invalid_token",
                    "message": "An API key was presented and not recognised.",
                    "hint": "Check the key, or send no credential at all: the public endpoints answer anonymous callers.",
                    "status": 401,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not scoped for this endpoint. (code `insufficient_scope`)",
            "headers": {
              "www-authenticate": {
                "description": "Bearer challenge (RFC 6750), carrying `error` and, on a scope refusal, `scope`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_scope",
                    "message": "The key is valid but not scoped for this endpoint.",
                    "hint": "Request a key carrying the scope named in the WWW-Authenticate header. Scopes are listed in /openapi.json.",
                    "status": 403,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is past the size cap. (code `payload_too_large`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is past the size cap.",
                    "hint": "Trim the body under the documented maximum and send it again.",
                    "status": 413,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input was readable but could not be used. (code `unprocessable_input`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unprocessable_input",
                    "message": "The input was readable but could not be used.",
                    "hint": "The request was well formed but the content could not be used. The message says what to change.",
                    "status": 422,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many runs. Wait and retry. (code `rate_limited`)",
            "headers": {
              "retry-after": {
                "description": "Whole seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many runs. Wait and retry.",
                    "hint": "Wait the number of seconds in the retry-after header, then retry.",
                    "status": 429,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "502": {
            "description": "A service this tool depends on failed. (code `upstream_error`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "upstream_error",
                    "message": "A service this tool depends on failed.",
                    "hint": "A service we depend on failed. Retry once after a short pause; if it repeats, the fault is ours.",
                    "status": 502,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has not switched the tool on. (code `not_configured`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_configured",
                    "message": "This deployment has not switched the tool on.",
                    "hint": "This deployment has not switched the feature on. Nothing to retry; the response will not change until it is.",
                    "status": 503,
                    "docs": "https://www.batmail.ai/openapi.json"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Batmail API key",
        "description": "A Batmail API key sent as `Authorization: Bearer <key>`.\n\nKeys are scoped. A key carrying `tools:run` may run every free tool; a key carrying `tools:<slug>:run` may run only that one. A key without a scope the endpoint accepts is refused with `insufficient_scope` and a `WWW-Authenticate` header naming what it needed.\n\nEvery scope this API defines is listed under `x-scopes` at the root of this document.",
        "x-scopes": [
          {
            "name": "tools:run",
            "summary": "Run any of the free email tools under /api/tools."
          },
          {
            "name": "tools:out-of-office-generator:run",
            "summary": "Run only the Out of Office Generator (POST /api/tools/out-of-office-generator)."
          },
          {
            "name": "tools:phishing-email-checker:run",
            "summary": "Run only the Phishing Email Checker (POST /api/tools/phishing-email-checker)."
          },
          {
            "name": "tools:gmail-filter-generator:run",
            "summary": "Run only the Gmail Filter Generator (POST /api/tools/gmail-filter-generator)."
          },
          {
            "name": "tools:email-header-analyzer:run",
            "summary": "Run only the Email Header Analyzer (POST /api/tools/email-header-analyzer)."
          },
          {
            "name": "tools:professional-email-rewriter:run",
            "summary": "Run only the Professional Email Rewriter (POST /api/tools/professional-email-rewriter)."
          },
          {
            "name": "tools:subject-line-tester:run",
            "summary": "Run only the Subject Line Tester (POST /api/tools/subject-line-tester)."
          }
        ]
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The same key as `bearerApiKey`, for clients that find a plain header easier to set. Identical scope rules apply.",
        "x-scopes": [
          {
            "name": "tools:run",
            "summary": "Run any of the free email tools under /api/tools."
          },
          {
            "name": "tools:out-of-office-generator:run",
            "summary": "Run only the Out of Office Generator (POST /api/tools/out-of-office-generator)."
          },
          {
            "name": "tools:phishing-email-checker:run",
            "summary": "Run only the Phishing Email Checker (POST /api/tools/phishing-email-checker)."
          },
          {
            "name": "tools:gmail-filter-generator:run",
            "summary": "Run only the Gmail Filter Generator (POST /api/tools/gmail-filter-generator)."
          },
          {
            "name": "tools:email-header-analyzer:run",
            "summary": "Run only the Email Header Analyzer (POST /api/tools/email-header-analyzer)."
          },
          {
            "name": "tools:professional-email-rewriter:run",
            "summary": "Run only the Professional Email Rewriter (POST /api/tools/professional-email-rewriter)."
          },
          {
            "name": "tools:subject-line-tester:run",
            "summary": "Run only the Subject Line Tester (POST /api/tools/subject-line-tester)."
          }
        ]
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "The shape of every failed call. `code` is stable and safe to branch on; `message` is the sentence a person would be shown; `hint` says what to change.",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always false on a failure."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_found",
                  "method_not_allowed",
                  "invalid_request",
                  "payload_too_large",
                  "rate_limited",
                  "unauthorized",
                  "invalid_token",
                  "insufficient_scope",
                  "not_configured",
                  "unprocessable_input",
                  "upstream_error",
                  "server_error"
                ],
                "description": "Stable identifier for the failure."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, in plain words."
              },
              "hint": {
                "type": "string",
                "description": "What to change before retrying."
              },
              "status": {
                "type": "integer",
                "description": "The HTTP status, repeated in the body."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Where this endpoint is described."
              }
            },
            "required": [
              "code",
              "message",
              "hint",
              "status",
              "docs"
            ]
          }
        },
        "required": [
          "ok",
          "error"
        ]
      }
    }
  },
  "security": [
    {},
    {
      "bearerApiKey": []
    },
    {
      "apiKeyHeader": []
    }
  ]
}