{
  "openapi": "3.1.0",
  "info": {
    "title": "Sovereign Mail (Sovmail) API",
    "version": "1.0.0",
    "description": "UK-sovereign transactional email. Start with GET /account to find a valid from-address and anything blocking live sending. Live keys (smk_live_…) deliver; test keys (smk_test_…) are validated and recorded but never delivered. No open or click tracking; links are never rewritten. Full guide for AI assistants: https://sovmail.co.uk/llms-full.txt"
  },
  "servers": [
    {
      "url": "https://sovmail.co.uk/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "externalDocs": {
    "description": "Integration guide (Markdown)",
    "url": "https://sovmail.co.uk/llms-full.txt"
  },
  "paths": {
    "/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "What this key can do",
        "description": "Key mode, verified sending domains, a suggestedFrom address that will be accepted, and blockers to live sending, each with a fix a human must do in the dashboard. Call this first.",
        "responses": {
          "200": {
            "description": "Account readiness",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/email": {
      "post": {
        "operationId": "sendEmail",
        "summary": "Send one email",
        "description": "Send one transactional email. 202 with status queued, test or suppressed is success in every case — suppressed means the recipient bounced or complained before and is deliberately skipped; do not retry it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Message"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Do not retry; fix the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "From-domain not verified, or account unverified, pending review, rejected or suspended. GET /account says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "templateId not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Content rejected by checks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily or per-minute limit reached. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/email/batch": {
      "post": {
        "operationId": "sendEmailBatch",
        "summary": "Send up to 500 emails",
        "description": "202 even when some messages fail: check each result's status and retry only entries with status error. Retrying the whole batch sends the others twice.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "messages"
                ],
                "properties": {
                  "messages": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/Message"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "One result per message, in request order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "to",
                          "status"
                        ],
                        "properties": {
                          "to": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "test",
                              "suppressed",
                              "error"
                            ]
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Do not retry; fix the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "List recent messages",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search recipient or subject",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "track",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageRecord"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Do not retry; fix the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List stored templates",
        "responses": {
          "200": {
            "description": "Templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "subject": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTemplate",
        "summary": "Create a template",
        "description": "Bodies support {{variable}}, {{#if key}}…{{/if}} and {{#unless key}}…{{/unless}}. Values are HTML-escaped in html. html or text is required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "subject"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "html": {
                    "type": "string"
                  },
                  "text": {
                    "type": "string"
                  },
                  "isLayout": {
                    "type": "boolean"
                  },
                  "layoutId": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Do not retry; fix the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "This month's usage",
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhook": {
      "put": {
        "operationId": "setWebhook",
        "summary": "Set the event webhook URL",
        "description": "Returns a secret. Events are POSTed with x-sovmail-signature: sha256=<HMAC-SHA256 of the raw body>. Best-effort: one attempt, no retries. Types: message.sent, message.delivered (tracked messages only).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "secret": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Do not retry; fix the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from the dashboard: smk_live_… or smk_test_…"
      }
    },
    "schemas": {
      "Message": {
        "type": "object",
        "required": [
          "from",
          "to"
        ],
        "description": "subject plus text and/or html, or templateId (+ variables) instead.",
        "properties": {
          "from": {
            "type": "string",
            "format": "email",
            "description": "Bare address on a verified domain. No display name."
          },
          "to": {
            "type": "string",
            "format": "email",
            "description": "Exactly one address."
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "templateId": {
            "type": "string",
            "format": "uuid"
          },
          "variables": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "track": {
            "type": "boolean",
            "description": "Record delivery confirmation. Never open/click tracking."
          }
        }
      },
      "SendResult": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "test",
              "suppressed"
            ],
            "description": "queued = accepted for delivery (every successful live send); test = test key, not delivered; suppressed = deliberately skipped."
          }
        }
      },
      "MessageRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "to": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sending",
              "sent",
              "delivered",
              "bounced",
              "failed",
              "suppressed",
              "test"
            ]
          },
          "track": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string"
          },
          "limit": {
            "type": "integer"
          },
          "used": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer"
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "key": {
            "type": "object",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ]
              },
              "note": {
                "type": "string"
              }
            }
          },
          "sendingDomains": {
            "type": "object",
            "properties": {
              "verified": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "pending": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "suggestedFrom": {
            "type": [
              "string",
              "null"
            ],
            "description": "A from-address that will be accepted."
          },
          "canSendTest": {
            "type": "boolean"
          },
          "canSendLive": {
            "type": "boolean"
          },
          "blockers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "account_suspended",
                    "account_rejected",
                    "account_pending_review",
                    "email_not_verified",
                    "no_verified_domain",
                    "quota_exhausted"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "fix": {
                  "type": "string",
                  "description": "What a human must do in the dashboard."
                }
              }
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "docs": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "issues": {
            "type": "array",
            "items": {},
            "description": "Validation details (400 only)."
          },
          "docs": {
            "type": "string",
            "description": "Link to the integration guide."
          }
        }
      }
    }
  }
}