{"openapi":"3.1.0","info":{"title":"FirstSales Public API","version":"1.0.0","description":"Read-only, unauthenticated JSON endpoints for email validation, 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. All endpoints are IP-rate-limited; see each operation's description and `x-rateLimit` extension for its exact limit.","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/"},"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."}],"paths":{"/api/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. Not rate-limited.","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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/public/tutorials":{"get":{"operationId":"listTutorials","summary":"List public tutorials","description":"Returns every published tutorial (slug, title, summary, steps, pro tips, FAQs) as structured JSON. Use this to enumerate available tutorial content before fetching one by slug. Not rate-limited; cached for 1 hour.","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"}}],"responses":{"200":{"description":"List of tutorials.","content":{"application/json":{"schema":{"type":"object","properties":{"tutorials":{"type":"array","items":{"$ref":"#/components/schemas/PublicTutorial"}}},"required":["tutorials"]}}}},"400":{"description":"Invalid request — bad input, missing field, or malformed value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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/429/500 error shape but the same Error schema) if the slug is unknown. Not rate-limited; cached for 1 hour.","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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No tutorial exists for this slug.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain not found in the RDAP registry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain does not exist or has no DNS records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Failed to query Certificate Transparency logs (crt.sh unavailable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"Certificate Transparency lookup timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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. Content must be 10-10000 characters. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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. 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."},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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. Rate limit: 30 requests/minute per IP.","tags":["Analyze"],"security":[],"x-rateLimit":{"limit":30,"windowSeconds":60},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/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. 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."},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","description":"Standard error envelope returned by every non-2xx response.","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"]},"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"]},"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"]}}}}