{"openapi":"3.1.0","info":{"title":"FirstSales Public API","version":"1.0.0","description":"Unauthenticated JSON endpoints (read-only, except the mail tester, which creates a temporary test inbox) for email validation, mail testing, DNS/deliverability diagnostics, domain intelligence, and AI-assisted cold-email content analysis on firstsales.io. Every endpoint here requires no API key — call it directly. Rate-limit policies and response headers are documented per operation; the mail-tester check has its own limiter without RateLimit or Retry-After headers. Versioning and deprecation are described by the `x-apiVersioning` and `x-deprecationPolicy` extensions, published both here and at the document root.","contact":{"name":"FirstSales Support","email":"firstsales@support.communicate.so","url":"https://firstsales.io"},"license":{"name":"Terms of Service","url":"https://firstsales.io/terms-of-service/"},"termsOfService":"https://firstsales.io/terms-of-service/","x-apiVersioning":{"strategy":"url-path","current":"v1","pattern":"https://firstsales.io/api/v1/{operation}","policyUrl":"https://firstsales.io/developers/#versioning","note":"A breaking change ships under a new prefix (/api/v2/); v1 keeps its contract. The unprefixed /api/{operation} form is an unversioned alias of the current version and may change without notice - call the /api/v1/ form."},"x-deprecationPolicy":{"minimumNoticeDays":180,"signalling":"A deprecated version's responses carry Deprecation: true and a Sunset header with the retirement date (RFC 8594), plus a Link rel=\"deprecation\" to the migration notes.","announcement":"https://firstsales.io/developers/#deprecation-policy","policyUrl":"https://firstsales.io/developers/#deprecation-policy","currentlyDeprecated":[]}},"x-apiVersioning":{"strategy":"url-path","current":"v1","pattern":"https://firstsales.io/api/v1/{operation}","policyUrl":"https://firstsales.io/developers/#versioning","note":"A breaking change ships under a new prefix (/api/v2/); v1 keeps its contract. The unprefixed /api/{operation} form is an unversioned alias of the current version and may change without notice - call the /api/v1/ form."},"x-deprecationPolicy":{"minimumNoticeDays":180,"signalling":"A deprecated version's responses carry Deprecation: true and a Sunset header with the retirement date (RFC 8594), plus a Link rel=\"deprecation\" to the migration notes.","announcement":"https://firstsales.io/developers/#deprecation-policy","policyUrl":"https://firstsales.io/developers/#deprecation-policy","currentlyDeprecated":[]},"servers":[{"url":"https://firstsales.io","description":"Production"}],"security":[],"tags":[{"name":"System","description":"Service health."},{"name":"Tutorials","description":"Public, no-auth tutorial content feed."},{"name":"Email","description":"Email address validation and risk checks."},{"name":"DNS","description":"DNS record and deliverability diagnostics."},{"name":"Domain","description":"Domain intelligence (age, records, subdomains)."},{"name":"Analyze","description":"AI-assisted cold-email content analysis."},{"name":"Mail Tester","description":"Temporary test inbox and received-email analysis."}],"paths":{"/api/v1/tools/mail-tester/generate":{"post":{"operationId":"createMailTestInbox","summary":"Create a temporary test inbox","description":"Step 1 of a mail test. No request body is required. Returns top-level emailId, emailAddress and expiresAt (Unix milliseconds); the session expires after seven days. Send an email to emailAddress, then poll check with emailId. Rate limit: 10 requests per minute per IP, plus an in-memory 50-per-hour check and a cap of 50 stored sessions created in the previous hour. The hourly in-memory check shares the legacy default limiter bucket. RateLimit headers describe the 10-per-minute wrapper; Retry-After is present only when that wrapper refuses the request.","tags":["Mail Tester"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60,"note":"Also checks an in-memory 50/hour limit in the shared default bucket and a cap of 50 stored sessions created in the previous hour per IP."},"responses":{"200":{"description":"The generated test inbox address and expiry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"emailId":{"type":"string"},"emailAddress":{"type":"string","format":"email"},"expiresAt":{"type":"integer","description":"Unix time in milliseconds."}},"required":["success","emailId","emailAddress","expiresAt"]}}}},"429":{"description":"Minute or hourly limit exceeded. Retry-After is supplied only for the minute-limit refusal.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/MailTesterError"}]}}}},"500":{"description":"Could not create the test inbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailTesterError"}}},"headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}}}}}},"/api/v1/tools/mail-tester/check":{"get":{"operationId":"getMailTestReport","summary":"Read the received emails for a test inbox","description":"Step 2 of a mail test. Returns {success, session}; poll until session.emails is not empty. Each email may include authentication results, spam score and analysis when available. Rate limit: 60 requests per minute per IP. This handler sends no RateLimit or Retry-After headers.","tags":["Mail Tester"],"security":[],"x-rateLimit":{"limit":60,"windowSeconds":60},"parameters":[{"name":"emailId","in":"query","required":true,"schema":{"type":"string"},"description":"The top-level emailId returned by generate."}],"responses":{"200":{"description":"The test session and its received emails.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"session":{"type":"object","properties":{"emailId":{"type":"string"},"emailAddress":{"type":"string","format":"email"},"createdAt":{"type":"integer","description":"Unix time in milliseconds."},"expiresAt":{"type":"integer","description":"Unix time in milliseconds."},"emails":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"subject":{"type":"string"},"receivedAt":{"type":"integer","description":"Unix time in milliseconds."},"headers":{"type":"object","additionalProperties":{"type":"string"}},"textBody":{"type":"string"},"htmlBody":{"type":"string"},"spamScore":{"type":"number"},"analysis":{"type":"object","properties":{"spfStatus":{"type":"string"},"dkimStatus":{"type":"string"},"dmarcStatus":{"type":"string"},"spamReasons":{"type":"array","items":{"type":"string"}},"recommendations":{"type":"array","items":{"type":"string"}},"aiInsights":{"type":"array","items":{"type":"string"}},"strengths":{"type":"array","items":{"type":"string"}}}}},"required":["id","from","to","subject","receivedAt"]}}},"required":["emailId","emailAddress","createdAt","expiresAt","emails"]}},"required":["success","session"]}}}},"400":{"description":"The emailId query parameter is missing or empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailTesterError"}}}},"404":{"description":"The session is missing, expired, or unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailTesterError"}}}},"429":{"description":"The 60-per-minute polling limit was exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailTesterError"}}}},"500":{"description":"Could not check the test inbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailTesterError"}}}}}}},"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Service health check","description":"Returns service status, ISO timestamp, and process uptime in seconds. Call this to confirm the API is reachable before calling other endpoints. Limited to 120 requests per minute per IP, and every response carries the RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy headers so an agent can read the live contract from a single unauthenticated GET.","tags":["System"],"security":[],"responses":{"200":{"description":"Service is healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/tutorials":{"get":{"operationId":"listTutorials","summary":"List public tutorials","description":"Returns published tutorial content (slug, title, summary, steps, pro tips, FAQs) as structured JSON. Omit pagination params to fetch the full edge-cached feed, or pass limit/cursor for deterministic cursor pagination over the same stable slug order. Served from the edge cache with a 1-hour revalidation window, so it carries no per-IP limiter and never answers 429.","tags":["Tutorials"],"security":[],"parameters":[{"name":"locale","in":"query","required":false,"description":"BCP-47 locale to overlay translated title/summary/content onto the English canonical tutorial.","schema":{"type":"string","example":"es"}},{"name":"limit","in":"query","required":false,"description":"Maximum tutorials to return in this page (1-100). Omit to return the full feed.","schema":{"type":"integer","minimum":1,"maximum":100,"example":20}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor returned by pageInfo.nextCursor from a previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of tutorials.","content":{"application/json":{"schema":{"type":"object","properties":{"tutorials":{"type":"array","items":{"$ref":"#/components/schemas/PublicTutorial"}},"pageInfo":{"$ref":"#/components/schemas/CursorPageInfo"}},"required":["tutorials","pageInfo"]}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/tutorials/{slug}":{"get":{"operationId":"getTutorialBySlug","summary":"Get one tutorial by slug","description":"Returns a single tutorial's full content (steps, pro tips, FAQs) by its slug. Call listTutorials first to discover valid slugs. Returns 404 (not this operation's 400/500 error shape but the same Error schema) if the slug is unknown. Served from the edge cache with a 1-hour revalidation window, so it carries no per-IP limiter and never answers 429.","tags":["Tutorials"],"security":[],"parameters":[{"name":"slug","in":"path","required":true,"description":"Tutorial slug, e.g. `cold-email-deliverability`.","schema":{"type":"string"}},{"name":"locale","in":"query","required":false,"description":"BCP-47 locale to overlay translated content onto the English canonical tutorial.","schema":{"type":"string","example":"es"}}],"responses":{"200":{"description":"The tutorial.","content":{"application/json":{"schema":{"type":"object","properties":{"tutorial":{"$ref":"#/components/schemas/PublicTutorial"}},"required":["tutorial"]}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No tutorial exists for this slug.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/email/validate":{"post":{"operationId":"validateEmail","summary":"Validate an email address's deliverability","description":"Checks email format, then resolves the domain's DNS to confirm it exists and has MX records, and flags reserved/disposable/free-provider domains. Use this before sending outreach to filter out addresses that cannot receive mail. Rate limit: 20 requests/minute per IP.","tags":["Email"],"security":[],"x-rateLimit":{"limit":20,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Validation result (still 200 even when the email is invalid — check `valid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailValidationResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/email/disposable":{"post":{"operationId":"checkDisposableEmail","summary":"Score an email domain for disposable/temporary-mailbox risk","description":"Runs six weighted checks (known disposable database, domain age via RDAP, MX validity, reputation, naming pattern, MX cross-reference) and returns a 0-100 score plus a safe/suspicious/disposable/high_risk classification. Use this to filter throwaway addresses out of a lead list. Rate limit: 20 requests/minute per IP.","tags":["Email"],"security":[],"x-rateLimit":{"limit":20,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Disposable-risk assessment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisposableCheckResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/email/catchall":{"post":{"operationId":"checkCatchAllDomain","summary":"Detect whether a domain accepts mail to any mailbox (catch-all)","description":"Sends live SMTP RCPT TO probes with random non-existent mailboxes at the domain's primary MX host and infers catch-all behavior from acceptance/rejection. Use this to avoid false-positive 'valid' verdicts on catch-all domains. Slower than the other email checks (real SMTP round-trips). Rate limit: 20 requests/minute per IP.","tags":["Email"],"security":[],"x-rateLimit":{"limit":20,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","example":"example.com"}},"required":["domain"]}}}},"responses":{"200":{"description":"Catch-all detection result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatchAllResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dns/check":{"post":{"operationId":"checkDnsRecord","summary":"Run one specific DNS/email-authentication/security check against a domain","description":"Single-check DNS diagnostic tool: pass a `checkType` (spf, dkim, dmarc, mx, general, blacklist, iprep, ptr, tls, bimi, arc, ns, txt, cname, a, aaaa, soa, whois, availability, propagation, authority, dnssec, deliverability, smtp, or port) and get back pass/warn/fail sub-checks plus a 0-100 score. Use `dns/validate` instead if you need SPF+DKIM+DMARC+MX together in one call. Rate limit: 10 requests/minute per IP.","tags":["DNS"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","description":"Domain name, or an IPv4 address when checkType is iprep/ptr."},"checkType":{"type":"string","enum":["spf","dkim","dmarc","mx","general","blacklist","iprep","ptr","tls","bimi","arc","ns","txt","cname","a","aaaa","soa","whois","availability","propagation","authority","dnssec","deliverability","smtp","port"]},"selector":{"type":"string","description":"Optional DKIM selector (alphanumeric + hyphens, max 63 chars); only used when checkType is dkim."}},"required":["domain","checkType"]}}}},"responses":{"200":{"description":"Check result for the requested checkType.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsCheckResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dns/dkim":{"post":{"operationId":"checkDkimRecord","summary":"Look up and validate a domain's DKIM record","description":"Auto-detects common DKIM selectors (or uses the selector you pass in manual mode), then reports key type, estimated key strength, testing-mode flag, and validity. Use this for a DKIM-focused check with more detail than dns/check's generic dkim type. Rate limit: 10 requests/minute per IP.","tags":["DNS"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"},"selector":{"type":"string","description":"DKIM selector, e.g. `google`. Required when mode is 'manual'."},"mode":{"type":"string","enum":["auto","manual"],"description":"Defaults to auto-detect of common selectors."}},"required":["domain"]}}}},"responses":{"200":{"description":"DKIM lookup result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DkimResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dns/validate":{"post":{"operationId":"validateDnsAuthentication","summary":"Run SPF, DKIM, DMARC, and MX checks together with a confidence score","description":"Multi-check email-authentication validator: runs SPF/DKIM/DMARC/MX in parallel (or a subset via `checks`) and returns a combined confidence_score. Use this for a one-call deliverability audit instead of four separate dns/check calls. Rate limit: 10 requests/minute per IP.","tags":["DNS"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"},"checks":{"type":"array","items":{"type":"string","enum":["spf","dkim","dmarc","mx"]},"description":"Subset of checks to run. Defaults to all four."},"options":{"type":"object","properties":{"dkimSelector":{"type":"string","description":"Optional DKIM selector override."}}}},"required":["domain"]}}}},"responses":{"200":{"description":"Combined authentication validation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsValidateResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dns/reverse":{"post":{"operationId":"reverseDnsLookup","summary":"Reverse-DNS (PTR) lookup for an IPv4 address, with forward-confirmation","description":"Looks up PTR hostnames for an IPv4 address and re-resolves each hostname's A records to check they point back to the original IP (forward-confirmed reverse DNS, a common sending-IP reputation signal). Rejects private/reserved IPs. Rate limit: 10 requests/minute per IP.","tags":["DNS"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","format":"ipv4","example":"203.0.113.10"}},"required":["ip"]}}}},"responses":{"200":{"description":"Reverse DNS lookup result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReverseDnsResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domain/age":{"post":{"operationId":"getDomainAge","summary":"Get a domain's registration age and expiration via RDAP","description":"Queries RDAP (the modern WHOIS replacement) for registration date, expiration date, last-updated date, registrar, and status, and computes age in days/months/years. Use this to flag newly-registered domains, a common spam/fraud signal. Rate limit: 10 requests/minute per IP.","tags":["Domain"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"}},"required":["domain"]}}}},"responses":{"200":{"description":"Domain age/registration details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainAgeResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain not found in the RDAP registry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domain/dns-records":{"post":{"operationId":"getDomainDnsRecords","summary":"Fetch every DNS record type for a domain in one call","description":"Queries A, AAAA, MX, NS, TXT, SOA, and CNAME records in parallel and returns them flattened, with a per-type summary count. Use this for a full DNS snapshot instead of calling dns/check once per record type. Rate limit: 10 requests/minute per IP.","tags":["Domain"],"security":[],"x-rateLimit":{"limit":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"}},"required":["domain"]}}}},"responses":{"200":{"description":"All discovered DNS records for the domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsRecordsResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domain/subdomains":{"post":{"operationId":"findSubdomains","summary":"Discover subdomains via Certificate Transparency logs","description":"Queries crt.sh Certificate Transparency logs for every certificate issued for the domain and extracts unique subdomains from SAN entries. Passive/OSINT technique — no active scanning. Slower than other domain checks (crt.sh can be slow) and more tightly rate-limited: 5 requests/minute per IP.","tags":["Domain"],"security":[],"x-rateLimit":{"limit":5,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"}},"required":["domain"]}}}},"responses":{"200":{"description":"Discovered subdomains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubdomainsResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Failed to query Certificate Transparency logs (crt.sh unavailable).","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"Certificate Transparency lookup timed out.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analyze/spam":{"post":{"operationId":"analyzeSpamScore","summary":"Score email content for spam-trigger risk","description":"AI-assisted analysis (with a deterministic local fallback when no AI key is configured) that flags spam trigger words, excessive capitalization, urgency language, and misleading claims, returning a 0-100 spam-risk score and improvement suggestions. This POST is side-effect-free and safe to retry with the same body. Content must be 10-10000 characters. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"x-idempotent":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional retry key accepted for agent/client compatibility. This analysis operation is side-effect-free, so repeating the same body is safe even without server-side key storage.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":10,"maxLength":10000}},"required":["content"]}}}},"responses":{"200":{"description":"Spam risk analysis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpamAnalysisResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analyze/subject-line":{"post":{"operationId":"analyzeSubjectLine","summary":"Score and improve a cold-email subject line","description":"AI-assisted analysis (with local fallback) returning a 0-100 effectiveness score, issues, strengths, improvement suggestions, and an optional rewritten version of the subject line. This POST is side-effect-free and safe to retry with the same body. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"x-idempotent":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional retry key accepted for agent/client compatibility. This analysis operation is side-effect-free, so repeating the same body is safe even without server-side key storage.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subjectLine":{"type":"string"}},"required":["subjectLine"]}}}},"responses":{"200":{"description":"Subject line analysis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentAnalysisResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analyze/cold-email":{"post":{"operationId":"analyzeColdEmail","summary":"Grade a full cold-email draft across 8 deliverability/effectiveness dimensions","description":"Local rule-based analysis (subject line, personalization, value proposition, CTA, length, tone, research signals, spam triggers) with an optional AI pass layered on top, returning a 0-100 score, letter grade, per-check detail, and per-dimension metrics. This POST is side-effect-free and safe to retry with the same body. Content must be at least 20 characters. Not rate-limited.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":null,"windowSeconds":null,"note":"No rate limiter on this route."},"x-idempotent":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional retry key accepted for agent/client compatibility. This analysis operation is side-effect-free, so repeating the same body is safe even without server-side key storage.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":20}},"required":["content"]}}}},"responses":{"200":{"description":"Cold email grade and analysis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColdEmailAnalysisResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analyze/email-body":{"post":{"operationId":"analyzeEmailBody","summary":"Score and improve a cold-email body","description":"AI-assisted analysis (with local fallback) of email body copy, returning a 0-100 effectiveness score, issues, strengths, improvement suggestions, and an optional rewritten version. This POST is side-effect-free and safe to retry with the same body. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"x-idempotent":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional retry key accepted for agent/client compatibility. This analysis operation is side-effect-free, so repeating the same body is safe even without server-side key storage.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emailBody":{"type":"string"}},"required":["emailBody"]}}}},"responses":{"200":{"description":"Email body analysis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentAnalysisResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analyze/personalization":{"post":{"operationId":"analyzePersonalization","summary":"Detect and score personalization signals in cold-email content","description":"Pattern-matches for recipient-name tokens, company references, pain-point mentions, social proof, CTA, and value proposition; layers an optional AI pass on top; returns a 0-100 score plus per-signal booleans, strengths, and suggestions. This POST is side-effect-free and safe to retry with the same body. Content must be at least 20 characters. Not rate-limited.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":null,"windowSeconds":null,"note":"No rate limiter on this route."},"x-idempotent":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional retry key accepted for agent/client compatibility. This analysis operation is side-effect-free, so repeating the same body is safe even without server-side key storage.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":20}},"required":["content"]}}}},"responses":{"200":{"description":"Personalization analysis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationAnalysisResult"}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint. Wait Retry-After seconds and retry.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Policy in the form \"<limit>;w=<window seconds>\".","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","description":"Shared error envelope used by public endpoints and the rate-limit wrapper. Mail-tester handler errors use MailTesterError.","properties":{"error":{"type":"string","description":"Machine-readable error code, e.g. `invalid_input`."},"message":{"type":"string","description":"Human-readable explanation."},"hint":{"type":"string","description":"How to fix the request."}},"required":["error","message","hint"]},"MailTesterError":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"string"},"message":{"type":"string"}},"required":["success","error","message"]},"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"},"uptime":{"type":"number","description":"Process uptime in seconds."}},"required":["status","timestamp","uptime"]},"PublicTutorial":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"category":{"type":"string"},"industry":{"type":"string"},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estMinutes":{"type":"number"},"steps":{"type":"array","items":{"type":"object"}},"proTips":{"type":"array","items":{"type":"object"}},"faqs":{"type":"array","items":{"type":"object"}}},"required":["slug","title","summary","category","difficulty","estMinutes","steps","proTips","faqs"]},"CursorPageInfo":{"type":"object","properties":{"nextCursor":{"type":["string","null"]},"prevCursor":{"type":["string","null"]},"hasMore":{"type":"boolean"},"total":{"type":"number"}},"required":["nextCursor","prevCursor","hasMore","total"]},"MxRecord":{"type":"object","properties":{"exchange":{"type":"string"},"priority":{"type":"number"}},"required":["exchange","priority"]},"EmailValidationResult":{"type":"object","properties":{"valid":{"type":"boolean"},"domain":{"type":"string"},"domainExists":{"type":"boolean"},"hasMx":{"type":"boolean"},"isReserved":{"type":"boolean"},"isDisposable":{"type":"boolean"},"isFreeProvider":{"type":"boolean"},"mxRecords":{"type":"array","items":{"$ref":"#/components/schemas/MxRecord"}},"error":{"type":"string"},"message":{"type":"string"}},"required":["valid","domain","domainExists","hasMx","isReserved","isDisposable","isFreeProvider"]},"DisposableCheckResult":{"type":"object","properties":{"score":{"type":"number","minimum":0,"maximum":100},"classification":{"type":"string","enum":["safe","suspicious","disposable","high_risk"]},"checks":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"status":{"type":"string","enum":["pass","warn","fail"]},"detail":{"type":"string"},"weight":{"type":"number"}},"required":["label","status","detail","weight"]}},"details":{"type":"object","properties":{"domain":{"type":"string"},"isDisposable":{"type":"boolean"},"domainAge":{"type":["object","null"],"properties":{"registeredAt":{"type":"string"},"ageDays":{"type":"number"}}},"mxRecords":{"type":"array","items":{"$ref":"#/components/schemas/MxRecord"}},"hasMx":{"type":"boolean"},"patternMatch":{"type":"boolean"},"databaseMatch":{"type":"boolean"}}}},"required":["score","classification","checks","details"]},"CatchAllResult":{"type":"object","properties":{"isCatchAll":{"type":"boolean"},"confidence":{"type":"string","enum":["high","medium","low"]},"checks":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"status":{"type":"string","enum":["pass","warn","fail"]},"detail":{"type":"string"}},"required":["label","status","detail"]}},"score":{"type":"number","minimum":0,"maximum":100}},"required":["isCatchAll","confidence","checks","score"]},"DnsCheckResult":{"type":"object","description":"Generic result shape shared by all dns/check checkTypes.","properties":{"exists":{"type":"boolean"},"records":{"type":"array","items":{"type":"string"}},"checks":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"status":{"type":"string","enum":["pass","warn","fail"]},"detail":{"type":"string"}},"required":["label","status","detail"]}},"score":{"type":"number","minimum":0,"maximum":100},"error":{"type":"string"},"message":{"type":"string"}},"required":["exists"]},"DkimResult":{"type":"object","properties":{"domain":{"type":"string"},"selector":{"type":"string"},"recordFound":{"type":"boolean"},"validStructure":{"type":"boolean"},"publicKeyPresent":{"type":"boolean"},"rawRecord":{"type":"string"},"status":{"type":"string","enum":["Valid","Invalid","Not Found"]},"details":{"type":"object","properties":{"versionTag":{"type":"boolean"},"keyType":{"type":"string"},"estimatedKeyBits":{"type":["number","null"]},"testingMode":{"type":"boolean"},"allSelectorsFound":{"type":"array","items":{"type":"string"}}}},"message":{"type":"string"}},"required":["domain","selector","recordFound","validStructure","publicKeyPresent","rawRecord","status"]},"DnsValidateResult":{"type":"object","properties":{"domain":{"type":"string"},"spf":{"type":"object"},"dkim":{"type":"object"},"dmarc":{"type":"object"},"mx":{"type":"object"},"confidence_score":{"type":"number","minimum":0,"maximum":100},"metadata":{"type":"object","properties":{"cached":{"type":"boolean"},"duration_ms":{"type":"number"}},"required":["cached","duration_ms"]}},"required":["domain","metadata"]},"ReverseDnsResult":{"type":"object","properties":{"ip":{"type":"string"},"hostnames":{"type":"array","items":{"type":"string"}},"forwardResults":{"type":"array","items":{"type":"object","properties":{"hostname":{"type":"string"},"forwardIps":{"type":"array","items":{"type":"string"}},"matches":{"type":"boolean"}},"required":["hostname","forwardIps","matches"]}},"ptrError":{"type":["string","null"]},"ipClass":{"type":"object"},"networkType":{"type":"string"}},"required":["ip","hostnames","forwardResults","networkType"]},"DomainAgeResult":{"type":"object","properties":{"domain":{"type":"string"},"registrationDate":{"type":["string","null"]},"expirationDate":{"type":["string","null"]},"lastUpdated":{"type":["string","null"]},"ageInDays":{"type":["number","null"]},"ageInMonths":{"type":["number","null"]},"ageInYears":{"type":["number","null"]},"registrar":{"type":["string","null"]},"status":{"type":"array","items":{"type":"string"}},"name":{"type":"string"}},"required":["domain","registrationDate","expirationDate","lastUpdated","ageInDays","ageInMonths","ageInYears","registrar","status","name"]},"DnsRecordsResult":{"type":"object","properties":{"domain":{"type":"string"},"records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["A","AAAA","MX","NS","TXT","SOA","CNAME"]},"value":{"type":"string"},"priority":{"type":"number"},"ttl":{"type":"number"}},"required":["type","value"]}},"summary":{"type":"object","description":"Count of records per type, e.g. { A: 1, MX: 2 }.","additionalProperties":{"type":"number"}},"totalRecords":{"type":"number"}},"required":["domain","records","summary","totalRecords"]},"SubdomainsResult":{"type":"object","properties":{"domain":{"type":"string"},"subdomains":{"type":"array","items":{"type":"string"}},"count":{"type":"number"},"source":{"type":"string","enum":["Certificate Transparency (crt.sh)"]}},"required":["domain","subdomains","count","source"]},"SpamAnalysisResult":{"type":"object","properties":{"score":{"type":"number","minimum":0,"maximum":100,"description":"0 = clean, 100 = definitely spam."},"triggerWords":{"type":"array","items":{"type":"string"}},"suggestions":{"type":"array","items":{"type":"string"}}},"required":["score","triggerWords","suggestions"]},"ContentAnalysisResult":{"type":"object","description":"Shared result shape for subject-line and email-body analysis.","properties":{"score":{"type":"number","minimum":0,"maximum":100},"suggestions":{"type":"array","items":{"type":"string"}},"issues":{"type":"array","items":{"type":"string"}},"strengths":{"type":"array","items":{"type":"string"}},"improvedVersion":{"type":"string"}},"required":["score","suggestions","issues","strengths"]},"ColdEmailAnalysisResult":{"type":"object","properties":{"score":{"type":"number","minimum":0,"maximum":100},"grade":{"type":"string","enum":["A","B","C","D","F"]},"checks":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"passed":{"type":"boolean"},"detail":{"type":"string"},"scoreImpact":{"type":"number"}},"required":["label","passed","detail","scoreImpact"]}},"strengths":{"type":"array","items":{"type":"string"}},"weaknesses":{"type":"array","items":{"type":"string"}},"improvements":{"type":"array","items":{"type":"string"}},"metrics":{"type":"object","properties":{"clarity":{"type":"number"},"personalization":{"type":"number"},"valueProp":{"type":"number"},"cta":{"type":"number"},"tone":{"type":"number"}}},"source":{"type":"string","enum":["ai+local","local"]}},"required":["score","grade","checks","strengths","weaknesses","improvements","metrics","source"]},"PersonalizationAnalysisResult":{"type":"object","properties":{"score":{"type":"number","minimum":0,"maximum":100},"personalization":{"type":"object","properties":{"recipientName":{"type":"boolean"},"companyMention":{"type":"boolean"},"painPoint":{"type":"boolean"},"socialProof":{"type":"boolean"},"cta":{"type":"boolean"},"valueProp":{"type":"boolean"}}},"strengths":{"type":"array","items":{"type":"string"}},"suggestions":{"type":"array","items":{"type":"string"}},"checks":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"status":{"type":"string","enum":["pass","warn","fail"]},"detail":{"type":"string"}},"required":["label","status","detail"]}},"source":{"type":"string","enum":["ai+local","local"]}},"required":["score","personalization","strengths","suggestions","checks","source"]}}}}