{
  "openapi": "3.1.0",
  "info": {
    "title": "Vetto",
    "version": "1.0.0",
    "summary": "Disposable, dead-domain and typo checks for signup emails.",
    "description": "Stops fake signups. The browser script tag is free for one site; the server endpoints /v1/check and /v1/verify need a Pro site's secret key. /v1/audit is free and keyless. Every client should fail open: treat errors and timeouts as allow. Human docs: https://usevetto.com/docs",
    "contact": { "name": "Vetto", "url": "https://usevetto.com", "email": "hello@usevetto.com" },
    "termsOfService": "https://usevetto.com/terms",
    "x-logo": { "url": "https://usevetto.com/logo-512.png" }
  },
  "externalDocs": { "url": "https://usevetto.com/docs" },
  "servers": [{ "url": "https://usevetto.com" }],
  "tags": [
    { "name": "check", "description": "Email verdicts for signup forms" },
    { "name": "lookup", "description": "Free domain lookup" }
  ],
  "paths": {
    "/v1/check": {
      "post": {
        "tags": ["check"],
        "operationId": "checkEmail",
        "summary": "Get a verdict for a signup email",
        "description": "With a secret key (server, Pro), commit defaults to true: the signup is recorded and counts toward the monthly quota of distinct addresses. Send commit=false for a read-only check. Over quota or when the site is paused, the response is verdict=allow with failOpen=true.",
        "security": [{ "secretKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": { "type": "string", "maxLength": 320, "examples": ["jane@mailinator.com"] },
                  "commit": { "type": "boolean", "default": true, "description": "Record the signup and issue a token" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResult" } } }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "tags": ["check"],
        "operationId": "verifyToken",
        "summary": "Verify the vetto_token posted with a signup form",
        "description": "The script tag adds a hidden vetto_token field to guarded forms. Tokens are bound to the email and site, valid for 15 minutes, and never issued for blocked verdicts. Pro only.",
        "security": [{ "secretKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token", "email"],
                "properties": {
                  "token": { "type": "string" },
                  "email": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "valid": { "type": "boolean" },
                    "reason": {
                      "type": ["string", "null"],
                      "enum": [null, "malformed", "bad_signature", "expired", "email_mismatch", "blocked", "wrong_site"]
                    },
                    "verdict": { "$ref": "#/components/schemas/Verdict" },
                    "reasons": { "type": "array", "items": { "$ref": "#/components/schemas/SignalCode" } },
                    "repeat": { "type": "integer" },
                    "expiresAt": { "type": ["integer", "null"], "description": "Unix seconds" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/audit": {
      "post": {
        "tags": ["lookup"],
        "operationId": "lookupDomains",
        "summary": "Check email domains for disposable providers and dead domains (free, no key)",
        "description": "Up to 50 unique domains per call; batches of about 20 work best. Domains marked deferred ran out of DNS budget in this call: send them again. Rate limit: 300 calls per day per IP.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domains"],
                "properties": {
                  "domains": {
                    "type": "array",
                    "maxItems": 50,
                    "items": { "type": "string" },
                    "examples": [["mailinator.com", "gmail.com", "doesnotexist-xyz-123.com"]]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-domain results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "results": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": ["ok", "skipped", "nxdomain", "no_mx_a", "null_mx", "no_mail", "deferred", "error"]
                          },
                          "mx": { "type": "array", "items": { "type": "string" } },
                          "disposable": { "type": "boolean" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "secretKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A site's secret key (sk_...) from the Vetto dashboard. Server-side only."
      }
    },
    "schemas": {
      "Verdict": { "type": ["string", "null"], "enum": ["allow", "flag", "suggest", "warn", "block", null] },
      "SignalCode": {
        "type": "string",
        "enum": ["invalid", "typo", "disposable", "dead", "relay", "bot", "lookalike", "repeat", "network", "burst", "freemail", "role", "denylist"]
      },
      "CheckResult": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "verdict": { "$ref": "#/components/schemas/Verdict" },
          "message": { "type": ["string", "null"], "description": "Text to show the user" },
          "suggestion": { "type": ["string", "null"], "description": "Corrected address for typos" },
          "reasons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": { "$ref": "#/components/schemas/SignalCode" },
                "action": { "type": "string" },
                "shadow": { "type": "boolean" }
              }
            }
          },
          "tags": { "type": "array", "items": { "type": "string" } },
          "repeat": { "type": "integer", "description": "Other aliases of the same Gmail inbox already signed up (Pro)" },
          "token": { "type": ["string", "null"], "description": "Signed verdict (Pro, committed, non-blocked checks)" },
          "branding": { "type": "boolean" },
          "failOpen": { "type": "boolean", "description": "Check skipped (paused or over quota); verdict is allow" },
          "reason": { "type": "string", "description": "Why the check failed open" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "const": false },
          "error": { "type": "string", "examples": ["bad_secret"] },
          "message": { "type": "string" }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
