{
  "openapi": "3.1.2",
  "info": {
    "title": "Mail Domain Check Public API",
    "summary": "Public email-domain authentication diagnostics",
    "description": "Checks public MX, SPF, DKIM, DMARC, MTA-STS, TLS-RPT, PTR, and CAA configuration. A focused sending-IP endpoint checks PTR and A/AAAA forward confirmation without requiring a domain. The public API returns summary fields only. Complete evidence and remediation actions are available through the browser report flow. Endpoints are rate-limited and intended for interactive diagnostics, not bulk enumeration.",
    "version": "1.0.0"
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://mail-domain-check-zac2.coral-ibis-2405.chatgpt.site",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Diagnostics",
      "description": "Public, read-only configuration diagnostics"
    },
    {
      "name": "Operations",
      "description": "Public service health"
    }
  ],
  "externalDocs": {
    "description": "API documentation and operating limits",
    "url": "https://mail-domain-check-zac2.coral-ibis-2405.chatgpt.site/developers/api/"
  },
  "paths": {
    "/api/ptr-check": {
      "post": {
        "operationId": "checkSendingIpReverseDns",
        "tags": ["Diagnostics"],
        "summary": "Check PTR and forward-confirmed reverse DNS",
        "description": "Checks one actual public outbound IPv4 or IPv6 address for PTR and then runs bounded A or AAAA lookups for the returned hostnames. The free response includes counts and confirmation state, but omits PTR hostnames, raw DNS answers, and remediation actions. Raw sending IPs are not retained in free analytics.",
        "parameters": [
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "Optional lowercase attribution bucket. It is stored only as a normalized aggregate source label.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PtrCheckRequest"
              },
              "example": {
                "sendingIp": "8.8.8.8"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Public PTR and forward-confirmation summary",
            "headers": {
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current source-address window",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PtrCheckSummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "$ref": "#/components/responses/RequestTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServiceError"
          }
        }
      }
    },
    "/api/scan": {
      "post": {
        "operationId": "scanMailDomain",
        "tags": ["Diagnostics"],
        "summary": "Scan public email-domain configuration",
        "description": "Runs eight public DNS and HTTPS checks. DKIM is evaluated only when a selector is supplied. PTR forward confirmation is evaluated only when the actual public outbound IP is supplied. Free analytics retain irreversible hashes and aggregate result metrics, not raw domains or sending IPs. Cross-origin browser requests are not supported; use the same-origin web app, the published CLI, or a server-side HTTP client.",
        "parameters": [
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "Optional lowercase attribution bucket. It is stored only as a normalized aggregate source label.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanRequest"
              },
              "examples": {
                "domainOnly": {
                  "summary": "Domain-only scan",
                  "value": {
                    "domain": "example.com"
                  }
                },
                "withSendingDetails": {
                  "summary": "Payload shape with optional DKIM and IP fields",
                  "value": {
                    "domain": "example.com",
                    "dkimSelector": "selector1",
                    "sendingIp": "8.8.8.8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Public scan summary",
            "headers": {
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current source-address window",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "Link": {
                "description": "Machine-readable API description",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanSummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "$ref": "#/components/responses/RequestTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServiceError"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getServiceHealth",
        "tags": ["Operations"],
        "summary": "Read public service health",
        "responses": {
          "200": {
            "description": "Current runtime, storage, and commerce readiness",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PtrCheckRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["sendingIp"],
        "properties": {
          "sendingIp": {
            "type": "string",
            "minLength": 3,
            "maxLength": 47,
            "description": "The actual public IPv4 or IPv6 address used to send mail. Private, loopback, link-local, and documentation ranges are rejected."
          }
        }
      },
      "ScanRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["domain"],
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 3,
            "maxLength": 253,
            "description": "A bare domain name without a URL scheme, path, email address, or trailing content. Unicode domains are accepted and normalized."
          },
          "dkimSelector": {
            "type": "string",
            "minLength": 1,
            "maxLength": 63,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$",
            "description": "A selector known from the mail provider or a real message header. The service does not guess selectors."
          },
          "sendingIp": {
            "type": "string",
            "minLength": 3,
            "maxLength": 45,
            "description": "The actual public IPv4 or IPv6 address used to send mail. Private, loopback, link-local, and documentation ranges are rejected."
          }
        }
      },
      "LocalizedText": {
        "type": "object",
        "additionalProperties": false,
        "required": ["zh", "en"],
        "properties": {
          "zh": {
            "type": "string"
          },
          "en": {
            "type": "string"
          }
        }
      },
      "StatusCounts": {
        "type": "object",
        "additionalProperties": false,
        "required": ["pass", "warn", "fail", "info"],
        "properties": {
          "pass": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "warn": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "fail": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "info": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          }
        }
      },
      "SpfDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["spfRecordCount", "dnsLookupCount", "dnsLookupLimit", "lookupComplete"],
        "properties": {
          "spfRecordCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of SPF policy records found at the requested domain"
          },
          "dnsLookupCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Potential DNS-causing SPF term count, or null when no unique policy can be expanded"
          },
          "dnsLookupLimit": {
            "type": "integer",
            "const": 10
          },
          "lookupComplete": {
            "type": "boolean",
            "description": "False when the include or redirect chain could not be expanded completely"
          }
        }
      },
      "DkimDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["recordCount", "keyType", "publicKeyPresent", "revoked", "testMode", "strictIdentity", "emailServiceAllowed", "sha256Allowed"],
        "properties": {
          "recordCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "TXT answers returned at the exact selector hostname, or null after an inconclusive DNS error"
          },
          "keyType": {
            "type": ["string", "null"],
            "enum": ["rsa", "ed25519", "unknown", null]
          },
          "publicKeyPresent": {
            "type": ["boolean", "null"],
            "description": "Whether a non-empty p tag is present; raw key material is never included"
          },
          "revoked": {
            "type": ["boolean", "null"],
            "description": "True only when an explicitly empty p tag revokes the selector"
          },
          "testMode": {
            "type": ["boolean", "null"],
            "description": "Whether the selector flags include t=y"
          },
          "strictIdentity": {
            "type": ["boolean", "null"],
            "description": "Whether the selector flags include t=s"
          },
          "emailServiceAllowed": {
            "type": ["boolean", "null"],
            "description": "Whether the s tag permits email or all services"
          },
          "sha256Allowed": {
            "type": ["boolean", "null"],
            "description": "Whether the h tag permits SHA-256, including the unrestricted default"
          }
        }
      },
      "DmarcDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["directRecordCount", "policyFound", "policyDomain", "inherited", "policyTag", "policy", "usesNoneFallback", "testMode", "dkimAlignment", "spfAlignment", "aggregateReporting", "treeWalkQueries", "conflictingRecords", "domainExistence", "legacyPctPresent"],
        "properties": {
          "directRecordCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "DMARC Policy Records at the exact Author Domain target, or null when that direct lookup failed"
          },
          "policyFound": {
            "type": ["boolean", "null"],
            "description": "Whether the RFC 9989 DNS Tree Walk selected one Policy Record, or null after an inconclusive DNS error"
          },
          "policyDomain": {
            "type": ["string", "null"],
            "description": "Domain whose Policy Record was selected; null when no record was selected"
          },
          "inherited": {
            "type": ["boolean", "null"],
            "description": "Whether the selected Policy Record came from a parent domain"
          },
          "policyTag": {
            "type": ["string", "null"],
            "enum": ["p", "sp", "np", null]
          },
          "policy": {
            "type": ["string", "null"],
            "enum": ["none", "quarantine", "reject", null],
            "description": "Applied policy, including an RFC 9989 none fallback, or null when no valid policy applies"
          },
          "usesNoneFallback": {
            "type": ["boolean", "null"],
            "description": "Whether a valid aggregate-report URI supplies the RFC 9989 p=none fallback for a missing or invalid policy tag"
          },
          "testMode": {
            "type": ["boolean", "null"],
            "description": "Whether the selected Policy Record requests t=y test mode"
          },
          "dkimAlignment": {
            "type": ["string", "null"],
            "enum": ["relaxed", "strict", "unknown", null]
          },
          "spfAlignment": {
            "type": ["string", "null"],
            "enum": ["relaxed", "strict", "unknown", null]
          },
          "aggregateReporting": {
            "type": ["boolean", "null"],
            "description": "Whether at least one syntactically valid aggregate-report URI is present; destinations are never included"
          },
          "treeWalkQueries": {
            "type": "integer",
            "minimum": 1,
            "maximum": 8,
            "description": "DMARC DNS targets queried during bounded RFC 9989 policy discovery"
          },
          "conflictingRecords": {
            "type": "boolean",
            "description": "Whether a queried target returned multiple DMARC Policy Records that must be discarded"
          },
          "domainExistence": {
            "type": ["string", "null"],
            "enum": ["exists", "nonexistent", "unknown", null],
            "description": "Author Domain existence classification used to select inherited sp or np behavior"
          },
          "legacyPctPresent": {
            "type": ["boolean", "null"],
            "description": "Whether the selected record contains the historic pct tag removed by RFC 9989"
          }
        }
      },
      "MtaStsDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["announcementRecordCount", "announcementValid", "policyFetchState", "policyValid", "policyMode", "maxAge", "mxPatternCount"],
        "properties": {
          "announcementRecordCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "STSv1 announcement records found at _mta-sts, or null after an inconclusive DNS error"
          },
          "announcementValid": {
            "type": ["boolean", "null"],
            "description": "Whether exactly one announcement has a valid RFC 8461 version and alphanumeric id"
          },
          "policyFetchState": {
            "type": "string",
            "enum": ["not_announced", "not_attempted", "fetched", "unreachable", "inconclusive"],
            "description": "Outcome of the fixed-path server-side HTTPS policy fetch"
          },
          "policyValid": {
            "type": ["boolean", "null"],
            "description": "Whether the fetched policy has valid required fields and MX patterns"
          },
          "policyMode": {
            "type": ["string", "null"],
            "enum": ["enforce", "testing", "none", null]
          },
          "maxAge": {
            "type": ["integer", "null"],
            "minimum": 0,
            "maximum": 31557600,
            "description": "Valid max_age in seconds, or null when unavailable or invalid"
          },
          "mxPatternCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Number of MX patterns in the fetched policy; pattern values are never included"
          }
        }
      },
      "TlsRptDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["recordCount", "configured", "destinationCount", "mailtoDestinationCount", "httpsDestinationCount"],
        "properties": {
          "recordCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "TLSRPTv1 records found at _smtp._tls, or null after an inconclusive DNS error"
          },
          "configured": {
            "type": ["boolean", "null"],
            "description": "Whether one TLSRPTv1 record has at least one valid mailto or HTTPS rua destination"
          },
          "destinationCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Valid report destination count; destination values are never included"
          },
          "mailtoDestinationCount": {
            "type": ["integer", "null"],
            "minimum": 0
          },
          "httpsDestinationCount": {
            "type": ["integer", "null"],
            "minimum": 0
          }
        }
      },
      "PtrDiagnostics": {
        "type": "object",
        "additionalProperties": false,
        "required": ["ipVersion", "ptrRecordCount", "forwardRecordType", "forwardQueryCount", "forwardQueriesCompleted", "forwardAddressCount", "forwardConfirmed"],
        "properties": {
          "ipVersion": {
            "type": "integer",
            "enum": [4, 6]
          },
          "ptrRecordCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Distinct PTR hostnames observed, or null after an inconclusive reverse lookup; hostnames are never included"
          },
          "forwardRecordType": {
            "type": "string",
            "enum": ["A", "AAAA"]
          },
          "forwardQueryCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8,
            "description": "Bounded number of PTR hostnames selected for forward lookup"
          },
          "forwardQueriesCompleted": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "forwardAddressCount": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "A or AAAA answers returned by completed forward lookups, or null when none completed; addresses are never included"
          },
          "forwardConfirmed": {
            "type": ["boolean", "null"],
            "description": "True when a forward answer equals the original sending IP, false after completed lookups with no match, or null when DNS errors prevent a conclusion"
          }
        }
      },
      "CheckSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "title", "status", "severity", "summary", "details"],
        "properties": {
          "id": {
            "type": "string",
            "enum": ["mx", "spf", "dmarc", "dkim", "mta-sts", "tls-rpt", "ptr", "caa"]
          },
          "title": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "status": {
            "type": "string",
            "enum": ["pass", "warn", "fail", "info"]
          },
          "severity": {
            "type": "string",
            "enum": ["critical", "high", "medium", "low", "info"]
          },
          "summary": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "details": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "diagnostics": {
            "oneOf": [
              { "$ref": "#/components/schemas/SpfDiagnostics" },
              { "$ref": "#/components/schemas/DkimDiagnostics" },
              { "$ref": "#/components/schemas/DmarcDiagnostics" },
              { "$ref": "#/components/schemas/MtaStsDiagnostics" },
              { "$ref": "#/components/schemas/TlsRptDiagnostics" },
              { "$ref": "#/components/schemas/PtrDiagnostics" }
            ],
            "description": "Present on SPF, DMARC, MTA-STS, TLS-RPT, PTR when a sending IP is supplied, and DKIM when a selector is supplied; raw records, policy contents, MX patterns, PTR hostnames, reporting destinations, key material, and remediation evidence remain excluded"
          }
        }
      },
      "ScanSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": ["domain", "unicodeDomain", "dkimSelector", "sendingIp", "scannedAt", "durationMs", "score", "grade", "counts", "summary", "checks", "disclaimer"],
        "properties": {
          "domain": {
            "type": "string",
            "description": "Normalized ASCII domain"
          },
          "unicodeDomain": {
            "type": "string",
            "description": "Normalized original input"
          },
          "dkimSelector": {
            "type": ["string", "null"]
          },
          "sendingIp": {
            "type": ["string", "null"]
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "durationMs": {
            "type": "integer",
            "minimum": 0
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "grade": {
            "type": "string",
            "enum": ["A", "B", "C", "D", "F"]
          },
          "counts": {
            "$ref": "#/components/schemas/StatusCounts"
          },
          "summary": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "checks": {
            "type": "array",
            "minItems": 8,
            "maxItems": 8,
            "items": {
              "$ref": "#/components/schemas/CheckSummary"
            }
          },
          "disclaimer": {
            "$ref": "#/components/schemas/LocalizedText"
          }
        }
      },
      "PtrCheckSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": ["sendingIp", "checkedAt", "durationMs", "check", "disclaimer"],
        "properties": {
          "sendingIp": {
            "type": "string",
            "description": "Normalized copy of the requested public IP"
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          },
          "durationMs": {
            "type": "integer",
            "minimum": 0
          },
          "check": {
            "$ref": "#/components/schemas/CheckSummary"
          },
          "disclaimer": {
            "$ref": "#/components/schemas/LocalizedText"
          }
        }
      },
      "Health": {
        "type": "object",
        "additionalProperties": false,
        "required": ["status", "version", "runtime", "storage", "commerce", "timestamp"],
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          },
          "version": {
            "type": "string"
          },
          "runtime": {
            "type": "string",
            "const": "worker"
          },
          "storage": {
            "type": "string",
            "enum": ["ready", "unavailable", "disabled"]
          },
          "commerce": {
            "type": "string",
            "enum": ["ready", "disabled"]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid domain, selector, IP, or JSON body",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RequestTooLarge": {
        "description": "Request body exceeds 8 KiB",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Content-Type must be application/json",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "More than 30 diagnostic or payment-confirmation requests were received from one source address within the current 60-second window",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the current window resets",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceError": {
        "description": "A DNS, HTTPS, storage, or runtime dependency is temporarily unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
