{"openapi":"3.0.3","info":{"title":"TLS Radar API","version":"1.0.0","description":"API for TLS Radar - SSL/TLS certificate monitoring service. Monitor certificate expirations, detect vulnerabilities, and receive alerts for your domains. Supports both public and private PKI certificates.","contact":{"name":"TLS Radar Support","url":"https://tlsradar.com/contact"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://tlsradar.com/api/v1","description":"Production"},{"url":"https://staging.tlsradar.com/api/v1","description":"Staging"}],"paths":{"/scan":{"post":{"tags":["Free Scanner"],"summary":"Scan a domain's SSL/TLS configuration","description":"Initiates a free, one-time SSL/TLS scan of any domain. Scans run asynchronously - the response returns immediately with status 202 (Accepted) and a share_token. Poll GET /scan/{share_token} until scan_status changes from 'pending' to 'success' or 'failed'. Results are cached. Rate limited to 20 requests/hour per IP, 10/hour per domain, 30/hour per device.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Domain to scan (e.g., example.com)","example":"example.com"}}}}}},"responses":{"200":{"description":"Cached scan result (already completed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeScanResult"}}}},"202":{"description":"Scan initiated - poll GET /scan/{share_token} for results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeScanResult"}}}},"422":{"description":"Invalid domain or scan failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded"}}}},"/scan/{share_token}":{"get":{"tags":["Free Scanner"],"summary":"Retrieve or poll a scan result by share token","description":"Returns a scan result by share token. Use this endpoint to poll for async scan completion - check scan_status field: 'pending' means still scanning, 'success' or 'failed' means done. Results are retained for 1 year. Share tokens are included in scan responses so results can be shared with colleagues.","security":[],"parameters":[{"name":"share_token","in":"path","required":true,"schema":{"type":"string"},"description":"Unique share token from a scan result"}],"responses":{"200":{"description":"Scan result (may be pending, success, or failed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeScanResult"}}}},"404":{"description":"Scan result not found"}}}},"/scan/{share_token}/pdf":{"get":{"tags":["Free Scanner"],"summary":"Download PDF report for a scan result","description":"Generates and returns a PDF report for a completed scan result. Returns 202 if the scan is still in progress.","security":[],"parameters":[{"name":"share_token","in":"path","required":true,"schema":{"type":"string"},"description":"Unique share token from a scan result"}],"responses":{"200":{"description":"PDF report file","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"202":{"description":"Scan is still in progress"},"404":{"description":"Scan result not found"}}}},"/scan/{share_token}/email_pdf":{"post":{"tags":["Free Scanner"],"summary":"Email PDF report for a scan result","description":"Sends a PDF report of the scan result to the specified email address.","security":[],"parameters":[{"name":"share_token","in":"path","required":true,"schema":{"type":"string"},"description":"Unique share token from a scan result"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Recipient email address","example":"security@company.com"}}}}}},"responses":{"200":{"description":"PDF report queued for delivery","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"domain":{"type":"string"},"email":{"type":"string","format":"email"}}}}}}}},"404":{"description":"Scan result not found"},"422":{"description":"Invalid email address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scan/{share_token}/email_link":{"post":{"tags":["Free Scanner"],"summary":"Email shareable link for a scan result","description":"Sends a shareable link to the scan result to the specified email address.","security":[],"parameters":[{"name":"share_token","in":"path","required":true,"schema":{"type":"string"},"description":"Unique share token from a scan result"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Recipient email address","example":"security@company.com"}}}}}},"responses":{"200":{"description":"Shareable link queued for delivery","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"domain":{"type":"string"},"email":{"type":"string","format":"email"}}}}}}}},"404":{"description":"Scan result not found"},"422":{"description":"Invalid email address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/info":{"get":{"tags":["Public"],"summary":"Get application information","description":"Returns general information about TLS Radar service","security":[],"responses":{"200":{"description":"Application information","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/pricing":{"get":{"tags":["Public"],"summary":"Get pricing information","description":"Returns all subscription tiers with pricing, limits, and features","security":[],"responses":{"200":{"description":"Pricing tiers","content":{"application/json":{"schema":{"type":"object","properties":{"currency":{"type":"string"},"tiers":{"type":"array","items":{"$ref":"#/components/schemas/PricingTier"}}}}}}}}}},"/features":{"get":{"tags":["Public"],"summary":"Get available features","description":"Returns all features and which tiers have access to them","security":[],"responses":{"200":{"description":"Feature list"}}}},"/me":{"get":{"tags":["User"],"summary":"Get current user information","description":"Returns information about the authenticated user","responses":{"200":{"description":"User information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Unauthorized"}}}},"/teams":{"get":{"tags":["Teams"],"summary":"List all teams","description":"Returns all teams the authenticated user belongs to","responses":{"200":{"description":"List of teams","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Team"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"tags":["Teams"],"summary":"Create a new team","description":"Creates a new team owned by the authenticated user","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["team"],"properties":{"team":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Team name"}}}}}}}},"responses":{"201":{"description":"Team created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/teams/{team_id}":{"get":{"tags":["Teams"],"summary":"Get team details","description":"Returns detailed information about a specific team","parameters":[{"$ref":"#/components/parameters/teamId"}],"responses":{"200":{"description":"Team details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Team not found"}}},"patch":{"tags":["Teams"],"summary":"Update team","description":"Updates team information (requires admin or owner role)","parameters":[{"$ref":"#/components/parameters/teamId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team":{"type":"object","properties":{"name":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Team updated"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"422":{"description":"Validation error"}}},"delete":{"tags":["Teams"],"summary":"Delete team","description":"Soft deletes a team (requires owner role)","parameters":[{"$ref":"#/components/parameters/teamId"}],"responses":{"204":{"description":"Team deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"}}}},"/teams/{team_id}/scan_groups":{"get":{"tags":["Scan Groups"],"summary":"List scan groups","description":"Returns all scan groups for a team","parameters":[{"$ref":"#/components/parameters/teamId"}],"responses":{"200":{"description":"List of scan groups"}}},"post":{"tags":["Scan Groups"],"summary":"Create scan group","description":"Creates a new scan group with domains to monitor","parameters":[{"$ref":"#/components/parameters/teamId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scan_group"],"properties":{"scan_group":{"type":"object","required":["friendly_name"],"properties":{"friendly_name":{"type":"string","description":"Name for the scan group"},"scan_port":{"type":"integer","default":443,"description":"Port to scan"},"hosts":{"type":"string","description":"Comma or space-separated list of domains"}}}}}}}},"responses":{"201":{"description":"Scan group created"},"422":{"description":"Validation error"}}}},"/teams/{team_id}/scan_groups/{id}":{"get":{"tags":["Scan Groups"],"summary":"Get scan group details","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Scan group details"}}},"patch":{"tags":["Scan Groups"],"summary":"Update scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Scan group updated"}}},"delete":{"tags":["Scan Groups"],"summary":"Delete scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"204":{"description":"Scan group deleted"}}}},"/teams/{team_id}/scan_groups/{id}/scan":{"post":{"tags":["Scans"],"summary":"Trigger scan for all domains in group","description":"Triggers an immediate TLS scan for all domains in the scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Scan triggered successfully"},"422":{"description":"Scan failed"},"429":{"description":"Rate limit exceeded"}}}},"/teams/{team_id}/scan_groups/{scan_group_id}/hosts":{"get":{"tags":["Hosts"],"summary":"List hosts in scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"}],"responses":{"200":{"description":"List of hosts"}}},"post":{"tags":["Hosts"],"summary":"Add host to scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["host"],"properties":{"host":{"type":"object","required":["address"],"properties":{"address":{"type":"string","description":"Domain name (e.g., example.com)"}}}}}}}},"responses":{"201":{"description":"Host added"}}}},"/teams/{team_id}/scan_groups/{scan_group_id}/hosts/{id}":{"get":{"tags":["Hosts"],"summary":"Get host details","description":"Returns detailed host information including latest scan result","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Host details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Host"}}}}}},"delete":{"tags":["Hosts"],"summary":"Remove host from scan group","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"204":{"description":"Host removed"}}}},"/teams/{team_id}/scan_groups/{scan_group_id}/hosts/{id}/scan":{"post":{"tags":["Scans"],"summary":"Trigger scan for single host","description":"Triggers an immediate TLS scan for a specific domain","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Scan triggered"},"422":{"description":"Scan failed"}}}},"/teams/{team_id}/scan_groups/{scan_group_id}/hosts/{host_id}/scan_results":{"get":{"tags":["Scans"],"summary":"List scan results for host","description":"Returns scan history for a specific host","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"},{"$ref":"#/components/parameters/hostId"}],"responses":{"200":{"description":"List of scan results"}}}},"/teams/{team_id}/scan_groups/{scan_group_id}/hosts/{host_id}/scan_results/{id}":{"get":{"tags":["Scans"],"summary":"Get scan result details","description":"Returns detailed scan data including certificate information","parameters":[{"$ref":"#/components/parameters/teamId"},{"$ref":"#/components/parameters/scanGroupId"},{"$ref":"#/components/parameters/hostId"},{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"Scan result details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResult"}}}}}}},"/api_keys":{"get":{"tags":["API Keys"],"summary":"List API keys","description":"Returns all active API keys for the authenticated user","responses":{"200":{"description":"List of API keys"}}},"post":{"tags":["API Keys"],"summary":"Create new API key","description":"Creates a new API key. The raw key is only shown once in the response.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["api_key"],"properties":{"api_key":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name for the API key"},"scopes":{"type":"array","items":{"type":"string","enum":["read","write","admin"]}},"team_id":{"type":"string","format":"uuid","description":"Scope key to specific team"},"expires_at":{"type":"string","format":"date-time","description":"Expiration date"}}}}}}}},"responses":{"201":{"description":"API key created. IMPORTANT: The raw_key is only shown once.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiKey"},"raw_key":{"type":"string","description":"Full API key (only shown once)"},"warning":{"type":"string"}}}}}}}}},"/api_keys/{id}":{"get":{"tags":["API Keys"],"summary":"Get API key details","parameters":[{"$ref":"#/components/parameters/resourceId"}],"responses":{"200":{"description":"API key details"}}},"delete":{"tags":["API Keys"],"summary":"Revoke API key","parameters":[{"$ref":"#/components/parameters/resourceId"}],"responses":{"204":{"description":"API key revoked"}}}},"/expiring":{"get":{"tags":["Expiring"],"summary":"List certificates expiring within N days","description":"Convenience query that flattens all monitored hosts the user can see and returns those whose latest scan shows an expiration date within the requested window. Sorted by days until expiration ascending.","parameters":[{"name":"within","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":365,"default":30},"description":"Days from now to look ahead"}],"responses":{"200":{"description":"List of expiring monitors with metadata"},"401":{"description":"Invalid or missing API key"}}}},"/mcp":{"post":{"tags":["MCP"],"summary":"Model Context Protocol JSON-RPC 2.0 endpoint","description":"Single JSON-RPC 2.0 endpoint that exposes TLS Radar tools to AI agents. Supports initialize, ping, tools/list, and tools/call. Bearer auth is required for tools that touch user data; public tools (scan) work without a token.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"description":"JSON-RPC request id (number, string, or null)"},"method":{"type":"string","example":"tools/call"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC response (result or error envelope)"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key obtained from TLS Radar dashboard"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 Authorization Code + PKCE. Public clients can self-register via RFC 7591 at /oauth/register. See /.well-known/oauth-authorization-server for the full metadata document.","flows":{"authorizationCode":{"authorizationUrl":"https://tlsradar.com/oauth/authorize","tokenUrl":"https://tlsradar.com/oauth/token","refreshUrl":"https://tlsradar.com/oauth/token","scopes":{"read":"Read access to scans, monitors, teams, and account","write":"Create and modify monitors, teams, scans","admin":"All read + write plus account-level operations"}}}}},"schemas":{"FreeScanResult":{"type":"object","properties":{"data":{"type":"object","properties":{"domain":{"type":"string","description":"Scanned domain"},"grade":{"type":"string","enum":["A+","A","B","C","D","F"],"nullable":true,"description":"Overall SSL/TLS grade (null while pending)"},"scan_status":{"type":"string","enum":["pending","success","failed"],"description":"Current scan status. Poll until not 'pending'."},"scanned_at":{"type":"string","format":"date-time"},"share_token":{"type":"string","description":"Token for sharing/polling this result"},"share_url":{"type":"string","format":"uri","description":"Shareable URL for this result"},"pdf_url":{"type":"string","format":"uri","nullable":true,"description":"PDF report download URL (null while pending)"},"expiration_date":{"type":"string","format":"date-time","nullable":true},"issues":{"type":"array","items":{"type":"object","properties":{"severity":{"type":"string","enum":["critical","high","warning","info"]},"message":{"type":"string"}}}},"certificate":{"type":"object","properties":{"common_name":{"type":"string"},"issuer":{"type":"string"},"valid_from":{"type":"string"},"valid_until":{"type":"string"},"expired":{"type":"boolean"},"matches_hostname":{"type":"boolean"},"trusted":{"type":"boolean"},"subject_alternative_names":{"type":"array","items":{"type":"string"}}}},"monitor_cta":{"type":"object","description":"Call-to-action for continuous monitoring signup","properties":{"message":{"type":"string"},"signup_url":{"type":"string","format":"uri"}}}}}}},"Team":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["active","disabled"]},"owner_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"stats":{"type":"object","properties":{"domains_count":{"type":"integer"},"members_count":{"type":"integer"},"scan_groups_count":{"type":"integer"}}}}},"ScanGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"scan_port":{"type":"integer","default":443},"status":{"type":"string","enum":["active","disabled"]},"hosts_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}},"Host":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"address":{"type":"string","description":"Domain name (e.g., example.com)"},"status":{"type":"string","enum":["active","disabled"]},"last_scan_status":{"type":"string","nullable":true},"last_scanned_at":{"type":"string","format":"date-time","nullable":true},"expiration_date":{"type":"string","format":"date-time","nullable":true},"expiring_soon":{"type":"boolean","description":"True if the certificate is unexpired and inside its expiry-warning window. The window opens shortly after an automated client should have renewed (a third of the lifetime left) and is never longer than 30 days: 28 days for a 90-day certificate, 19 for a 64-day one"}}},"ScanResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"scanned_at":{"type":"string","format":"date-time"},"status":{"type":"string"},"host_address":{"type":"string"},"expiration_date":{"type":"string","format":"date-time","nullable":true},"elapsed_time_seconds":{"type":"number"},"scan_data":{"type":"object","description":"Full scan data (detailed view only)"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"key_prefix":{"type":"string","description":"First 8 characters of the key"},"scopes":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked"]},"team_id":{"type":"string","format":"uuid","nullable":true},"last_used_at":{"type":"string","format":"date-time","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"status":{"type":"string"},"email_confirmed":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"teams_count":{"type":"integer"},"api_access":{"type":"boolean"},"subscription":{"type":"object","nullable":true,"properties":{"plan":{"type":"string"},"status":{"type":"string"},"current_period_end":{"type":"string","format":"date-time"}}}}},"PricingTier":{"type":"object","properties":{"tier":{"type":"string"},"name":{"type":"string"},"tagline":{"type":"string"},"recommended":{"type":"boolean"},"pricing":{"type":"object"},"limits":{"type":"object"},"features":{"type":"array","items":{"type":"string"}},"api_access":{"type":"boolean"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"errors":{"type":"array","items":{"type":"string"}}}}},"parameters":{"teamId":{"name":"team_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team UUID"},"scanGroupId":{"name":"scan_group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Scan group UUID"},"hostId":{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Host UUID"},"resourceId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Resource UUID"}},"x-mcp-tools":[{"name":"scan_domain","description":"Run a free, anonymous SSL/TLS scan against a hostname and return certificate details. No account required.","inputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"Hostname to scan (e.g. example.com). No scheme, no path."},"client_id":{"type":"string","description":"Optional anonymous install id from ~/.config/tlsradar/install_id. Pass it for funnel attribution. If you omit it, the response's install_id is a fresh one to save there."}},"required":["domain"]},"outputSchema":{"type":"object","properties":{"domain":{"type":"string"},"status":{"type":"string","enum":["pending","completed"]},"share_token":{"type":"string"},"share_url":{"type":"string","format":"uri"},"expiration_date":{"type":["string","null"],"format":"date-time"},"scanned_at":{"type":["string","null"],"format":"date-time"},"install_id":{"type":"string","description":"Anonymous install id to persist locally and reuse."}},"required":["domain","status","share_token","share_url"]},"annotations":{"title":"SSL/TLS Scan","readOnlyHint":true,"openWorldHint":true}},{"name":"create_certificate","description":"Start issuing a FREE Let's Encrypt certificate for a domain (no account required). Step 1 of 3. Lifetime is Let's Encrypt's default: 90 days today, 64 days from 2027-02-10; read the real expiry from the issued certificate.\nPick a validation method with `challenge`: \"dns-01\" (default; publish a TXT record; covers apex + www) or \"http-01\" (serve a file over HTTP on port 80; issues the exact domain only). dns-01 with a DNS-provider API token is the most automatable; http-01 suits a server you control on port 80.\nReturns an order_id plus either dns_records (dns-01) or http_files (http-01) to put in place. Next: poll `check_certificate_propagation` until all_found, then call `finalize_certificate`.\nStrongly prefer the CSR path at finalize (the private key never leaves the user's machine). Issuing automatically offers the user ongoing monitoring by email once it completes - don't add a monitor manually afterward.\n","inputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"Apex domain, no scheme/www (e.g. example.com)."},"email":{"type":"string","description":"Contact email for Let's Encrypt expiry notices and the monitoring handoff."},"challenge":{"type":"string","enum":["dns-01","http-01"],"description":"Validation method: dns-01 (default) or http-01."},"marketing_consent":{"type":"boolean","description":"Only true if the user explicitly opts in to a free account + reminder email. Default false."},"client_id":{"type":"string","description":"Optional anonymous install id from ~/.config/tlsradar/install_id (funnel attribution). If omitted, the response's install_id is a fresh one to save there."}},"required":["domain","email"]},"outputSchema":{"type":"object","properties":{"order_id":{"type":"string"},"domain":{"type":"string"},"challenge":{"type":"string"},"dns_records":{"type":"array","items":{"type":"object"},"description":"TXT records to publish for dns-01."},"http_files":{"type":"array","items":{"type":"object"},"description":"Files to serve for http-01."},"resume_token":{"type":"string","description":"Signed token to finalize past the backend's order TTL."},"install_id":{"type":"string"},"next_action":{"type":"string"}},"required":["order_id","domain","challenge"]},"annotations":{"title":"Start Certificate Issuance","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true}},{"name":"check_certificate_propagation","description":"Check whether the DNS TXT records for a certificate order have propagated (Cloudflare/Google/Quad9). Step 2 of issuance - poll until all_found is true, then call finalize_certificate. Returns per-record resolver results.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The order_id from create_certificate."}},"required":["order_id"]},"outputSchema":{"type":"object","properties":{"all_found":{"type":"boolean","description":"True when every challenge record/file is in place."},"records":{"type":"array","items":{"type":"object"},"description":"Per-record propagation status."}},"additionalProperties":true},"annotations":{"title":"Check Challenge Propagation","readOnlyHint":true,"idempotentHint":true,"openWorldHint":true}},{"name":"finalize_certificate","description":"Finalize and issue a certificate order in one call: validates the DNS challenges, waits for Let's Encrypt, and returns the issued cert. Step 3 of issuance - call after check_certificate_propagation reports all_found.\nSTRONGLY PREFER passing csr_pem (generate the key + CSR locally with openssl so the private key never leaves the machine). Returns leaf_pem/chain_pem/fullchain_pem. If you must, pass a passphrase instead to get a PKCS#12 bundle - but a CSR is safer.\nIf it replies \"still validating\", DNS hasn't fully propagated: re-check check_certificate_propagation and call again.\nNeeds a locally-generated CSR (csr_pem) - requires a local shell with openssl. On a surface without one (e.g. a Claude.ai custom connector) this can't complete; it returns guidance to finish in Claude Code/Cowork or the web form. Scanning and monitoring work everywhere.\nOn success the structuredContent carries a `handoff` object - relay `handoff.message` to the user and do NOT separately call add_monitor; the cert→monitoring handoff is automatic and server-side.\n","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The order_id from create_certificate."},"csr_pem":{"type":"string","description":"PEM CERTIFICATE REQUEST covering exactly {domain, www.domain}. Preferred - key stays local."},"passphrase":{"type":"string","description":"Fallback only: ≥8 chars, protects a returned PKCS#12 bundle. Omit when using csr_pem."},"max_wait_seconds":{"type":"integer","description":"How long to wait for validation server-side. Default 60, capped at 75."},"resume_token":{"type":"string","description":"Optional. The resume_token from create_certificate; pass it to finalize an order whose row Beacon already purged (~24h)."}},"required":["order_id"]},"outputSchema":{"type":"object","properties":{"state":{"type":"string"},"mode":{"type":"string"},"fullchain_pem":{"type":"string","description":"Full certificate chain (PEM), present on success."},"leaf_pem":{"type":"string"},"chain_pem":{"type":"string"},"not_after":{"type":"string","format":"date-time"},"handoff":{"type":"object","description":"Cert-\u003emonitoring handoff; relay handoff.message and do not call add_monitor."}},"additionalProperties":true},"annotations":{"title":"Finalize Certificate","readOnlyHint":false,"idempotentHint":true,"openWorldHint":true}},{"name":"get_certificate_status","description":"Return the current state of a certificate order (dns_pending, validating, ready, completed, failed) and per-authorization Let's Encrypt statuses. Use it to resume an interrupted issuance.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The order_id from create_certificate."}},"required":["order_id"]},"outputSchema":{"type":"object","properties":{"state":{"type":"string"},"challenge":{"type":"string"},"fullchain_pem":{"type":"string","description":"Present once the order is completed."}},"additionalProperties":true},"annotations":{"title":"Certificate Order Status","readOnlyHint":true,"idempotentHint":true,"openWorldHint":true}},{"name":"renew_certificate","description":"Renew a certificate by cloning a recent order (requires the original order_id; Beacon purges orders after ~24h). Returns a new order_id and fresh DNS TXT records - then poll check_certificate_propagation and call finalize_certificate. If you don't have an order_id (the usual case at renewal time), call create_certificate for the domain instead; that IS the renewal.","inputSchema":{"type":"object","properties":{"order_id":{"type":"string","description":"The original order_id to clone. If you don't have one, use create_certificate instead."}},"required":["order_id"]},"outputSchema":{"type":"object","properties":{"order_id":{"type":"string"},"challenge":{"type":"string"},"dns_records":{"type":"array","items":{"type":"object"}},"http_files":{"type":"array","items":{"type":"object"}},"state":{"type":"string"}},"additionalProperties":true},"annotations":{"title":"Renew Certificate","readOnlyHint":false,"idempotentHint":false,"openWorldHint":true}},{"name":"get_account","description":"Return the current user's plan, limits, and usage so the client can render upgrade nudges proactively.","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"email":{"type":"string"},"plan":{"type":"object","description":"Plan tier and limits."},"usage":{"type":"object","description":"Current usage against the plan limits."}},"required":["email"],"additionalProperties":true},"annotations":{"title":"Account \u0026 Plan Info","readOnlyHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"list_monitors","description":"List all certificates currently being monitored across the user's teams.\nIf the response's structuredContent includes a `nudge` object, the user is at their monitor cap - surface it casually ONCE (lead with `nudge.recommended_upgrade`, mention `nudge.also_available` in one closing line); don't force it if it doesn't fit the conversation.\n","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"monitors":{"type":"array","items":{"type":"object","properties":{"host_id":{"type":"string"},"address":{"type":"string"},"expiration_date":{"type":["string","null"],"format":"date-time"},"days_until_expiration":{"type":["integer","null"]},"scan_group_id":{"type":"string"},"team_id":{"type":"string"}}}},"count":{"type":"integer"},"nudge":{"type":"object","description":"Present only when an upgrade nudge is warranted."}},"required":["monitors","count"]},"annotations":{"title":"List Monitored Domains","readOnlyHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"add_monitor","description":"Add a domain to ongoing certificate monitoring with expiry alerts. Requires authentication (the user runs /mcp once).\nIf the plan's monitor limit is reached, the response's structuredContent carries a limit-reached payload - when relaying it, LEAD with `recommended_upgrade` (typically Starter, $9.99/mo), mention `also_available` tiers in a single closing line, and offer removing an existing monitor as the free alternative. Don't dump a full tier comparison; that's choice paralysis at the moment of action.\n","inputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"Hostname to monitor (e.g. example.com). No scheme, no path."}},"required":["domain"]},"outputSchema":{"type":"object","properties":{"host_id":{"type":"string"},"address":{"type":"string"},"scan_group_id":{"type":"string"},"team_id":{"type":"string"}},"additionalProperties":true},"annotations":{"title":"Add Domain to Monitoring","readOnlyHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"add_monitors","description":"Add multiple domains to monitoring in one call. Returns a per-domain status so the caller can show partial-success outcomes. Honors the same plan-limit checks as add_monitor.","inputSchema":{"type":"object","properties":{"domains":{"type":"array","items":{"type":"string"},"description":"List of hostnames to monitor","minItems":1,"maxItems":100}},"required":["domains"]},"outputSchema":{"type":"object","properties":{"requested":{"type":"integer"},"added":{"type":"integer"},"results":{"type":"array","items":{"type":"object"},"description":"Per-domain add status."},"team_id":{"type":"string"},"scan_group_id":{"type":"string"}},"required":["requested","added","results"]},"annotations":{"title":"Add Multiple Domains","readOnlyHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"remove_monitor","description":"Stop monitoring a domain. Accepts the domain name or the host_id returned by list_monitors.","inputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"Domain to stop monitoring"},"host_id":{"type":"string","description":"UUID of the host (alternative to domain)"}}},"outputSchema":{"type":"object","properties":{"removed_address":{"type":"string"}},"required":["removed_address"]},"annotations":{"title":"Stop Monitoring a Domain","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"list_expiring_certificates","description":"Return monitored certificates expiring within N days. Defaults to 30.\nIf the response's structuredContent includes a `nudge` object, the user is watching enough soon-to-expire certs to benefit from a higher tier - mention it casually ONCE (lead with `nudge.recommended_upgrade`); skip it if it doesn't fit.\n","inputSchema":{"type":"object","properties":{"within":{"type":"integer","description":"Days from now to look ahead","minimum":1,"maximum":365,"default":30}},"required":[]},"outputSchema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object"}},"within_days":{"type":"integer"},"count":{"type":"integer"},"nudge":{"type":"object","description":"Present only when an upgrade nudge is warranted."}},"required":["entries","within_days","count"]},"annotations":{"title":"Expiring Certificates","readOnlyHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"get_scan_history","description":"Return recent scan results for a domain the user monitors. Useful for spotting issuer changes, grade drops, or vulnerability appearances over time.","inputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"Domain name as it appears in list_monitors"},"limit":{"type":"integer","description":"Max results to return","minimum":1,"maximum":50,"default":10}},"required":["domain"]},"outputSchema":{"type":"object","properties":{"domain":{"type":"string"},"results":{"type":"array","items":{"type":"object","properties":{"scanned_at":{"type":"string","format":"date-time"},"scan_status":{"type":"string"},"expiration_date":{"type":["string","null"],"format":"date-time"},"grade":{"type":["string","null"]},"issuer":{"type":["string","null"]}}}},"count":{"type":"integer"}},"required":["domain","results","count"]},"annotations":{"title":"Scan History","readOnlyHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"export_monitors","description":"Dump the user's monitors as a JSON structure suitable for backup, migration, or infrastructure-as-code workflows. Tokens and PII are NEVER included - only domain configuration.","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"version":{"type":"string"},"exported_at":{"type":"string","format":"date-time"},"teams":{"type":"array","items":{"type":"object"}}},"required":["version","exported_at","teams"]},"annotations":{"title":"Export Monitors","readOnlyHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"import_monitors","description":"Create monitors from a JSON structure (typically produced by `export`). Skips domains the user is already monitoring; honors the plan's domain limit. Returns a per-domain status.","inputSchema":{"type":"object","properties":{"payload":{"type":"object","description":"Export payload, version 1.0. Use the `export` tool to generate one."}},"required":["payload"]},"outputSchema":{"type":"object","properties":{"requested":{"type":"integer"},"added":{"type":"integer"},"results":{"type":"array","items":{"type":"object"},"description":"Per-domain import status."}},"additionalProperties":true},"annotations":{"title":"Import Monitors","readOnlyHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"invite_team_member","description":"Invite a user to a team by email. Defaults to the user's current team. Honors the plan's seat limit (returns the same upgrade payload as add_monitor when the cap is hit).","inputSchema":{"type":"object","properties":{"email":{"type":"string","description":"Email address of the person to invite"},"team_id":{"type":"string","description":"Team UUID; defaults to the current team"},"role":{"type":"string","enum":["guest","admin"],"default":"guest","description":"Invitee role: guest or admin. Defaults to guest."}},"required":["email"]},"outputSchema":{"type":"object","properties":{"team_id":{"type":"string"},"team_name":{"type":"string"},"invited_email":{"type":"string"},"role":{"type":"string"}},"required":["team_id","invited_email","role"]},"annotations":{"title":"Invite Teammate","readOnlyHint":false,"idempotentHint":false,"openWorldHint":false}}]},"security":[{"oauth2":["read","write"]},{"bearerAuth":[]}],"tags":[{"name":"Public","description":"Publicly accessible endpoints (no authentication required)"},{"name":"Free Scanner","description":"Free SSL/TLS scanner - no authentication required"},{"name":"Teams","description":"Team management operations"},{"name":"Scan Groups","description":"Domain monitoring group operations"},{"name":"Hosts","description":"Individual domain/host operations"},{"name":"Scans","description":"Scan operations and results"},{"name":"API Keys","description":"API key management"},{"name":"User","description":"Current user information"},{"name":"Expiring","description":"Convenience: certificates expiring within a window"},{"name":"MCP","description":"Model Context Protocol (JSON-RPC 2.0) transport for AI agents"}]}