{"openapi":"3.1.0","info":{"title":"Email Spam Tester API","description":"\nTest an email before you send it to anybody.\n\nThe loop is three calls. Ask for an address, send the message you were about to\nsend your list to that address, then read the report. No key, no account, no\nlimit per person on the first 100,000 reports.\n\n    curl -X POST https://email-spam-tester.com/api/v1/inbox\n    # send your message to the address it returns, then:\n    curl https://email-spam-tester.com/api/v1/tests/<slug>\n\nThe address accepts exactly one message and expires after an hour. The report\nlink is permanent and carries `report_url` inside the JSON, so a forwarded\nreport still says where it came from.\n\n**Poll `/status`, not `/`.** The full report is several thousand lines of JSON\nand the analysis takes a few seconds; the status endpoint is the cheap way to\nfind out whether it is worth fetching yet. `analysis_status` reaches\n`checks_ready` first and the AI plan lands a minute or so later under\n`ai_status`, so a caller that only wants the checks does not have to wait for\nthe plan.\n\n**Agents:** there is an MCP server at `/mcp` with the same capability and a\nblocking wait, which is a better fit than polling from a model.\n\nEvery finding carries `citations`: the section of the RFC it comes from, quoted,\nand the page where Google states its own requirement. Those are curated by hand\nrather than generated, so they can be relied on.\n","contact":{"name":"Email Spam Tester","url":"https://email-spam-tester.com/"},"version":"1.0.0"},"paths":{"/api/health":{"get":{"summary":"Health","description":"Shallow. The container healthcheck polls this every few seconds and it\nmust not open a database connection each time.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Api Health Get"}}}}}}},"/api/health/deep":{"get":{"summary":"Deep","description":"What an external monitor should watch.","operationId":"deep_api_health_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deep Api Health Deep Get"}}}}}}},"/api/v1/inbox":{"post":{"summary":"Create Inbox","operationId":"create_inbox_api_v1_inbox_post","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"en","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tests/{slug}/locale":{"put":{"summary":"Set Locale","description":"Change which language the report and the AI plan will be written in.\n\nThe language is fixed when the address is handed out, because the SMTP\nconnection that arrives ten minutes later carries no hint of who is\nwaiting for it. That leaves one gap: somebody reserves an address, then\nswitches the page to their own language before sending. Without this they\nwould read a German page and get an English plan.\n\nOnly while the reservation is unused. Once the mail has arrived the\nanalysis is either running or done, and rewriting the language then would\nmean re-running it — a different operation, and one nobody asked for.\n\nThe slug is the capability, as everywhere else here: whoever holds the\naddress holds the test. There is nothing to leak — a wrong guess sets the\nlanguage of a report the guesser cannot read.","operationId":"set_locale_api_v1_tests__slug__locale_put","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocaleIn"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tests/{slug}/status":{"get":{"summary":"Test Status","operationId":"test_status_api_v1_tests__slug__status_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tests/{slug}/message":{"get":{"summary":"Message","description":"The message itself: headers, both bodies, and the source verbatim.\n\nSeparate from the report rather than a field on it, because the report is\npolled every two seconds while the analysis runs and a megabyte of source\non every poll would be paid for by every user to serve the few who open the\npanel. This is fetched once, when they do.","operationId":"message_api_v1_tests__slug__message_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageViewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tests/{slug}":{"get":{"summary":"Report","operationId":"report_api_v1_tests__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AttachmentOut":{"properties":{"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"type":"integer","title":"Size Bytes"},"inline":{"type":"boolean","title":"Inline"}},"type":"object","required":["filename","content_type","size_bytes","inline"],"title":"AttachmentOut"},"CheckOut":{"properties":{"id":{"type":"string","title":"Id"},"category":{"type":"string","title":"Category"},"status":{"type":"string","title":"Status"},"title":{"type":"string","title":"Title"},"summary":{"type":"string","title":"Summary"},"summary_localised":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary Localised"},"weight_compat":{"type":"number","title":"Weight Compat"},"weight_ours":{"type":"number","title":"Weight Ours"},"evidence":{"additionalProperties":true,"type":"object","title":"Evidence"},"citations":{"additionalProperties":true,"type":"object","title":"Citations","default":{}}},"type":"object","required":["id","category","status","title","summary","weight_compat","weight_ours","evidence"],"title":"CheckOut"},"EngineOut":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","title":"Status"},"summary":{"type":"string","title":"Summary"},"evidence":{"additionalProperties":true,"type":"object","title":"Evidence"},"scores":{"type":"boolean","title":"Scores"}},"type":"object","required":["id","title","status","summary","evidence","scores"],"title":"EngineOut"},"FixOut":{"properties":{"id":{"type":"string","title":"Id"},"fix":{"additionalProperties":true,"type":"object","title":"Fix"},"gain_compat":{"type":"number","title":"Gain Compat"},"gain_ours":{"type":"number","title":"Gain Ours"},"source":{"type":"string","title":"Source"}},"type":"object","required":["id","fix","gain_compat","gain_ours","source"],"title":"FixOut"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HeaderOut":{"properties":{"name":{"type":"string","title":"Name"},"value":{"type":"string","title":"Value"},"forged":{"type":"boolean","title":"Forged"}},"type":"object","required":["name","value","forged"],"title":"HeaderOut"},"InboxResponse":{"properties":{"address":{"type":"string","title":"Address"},"slug":{"type":"string","title":"Slug"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["address","slug","expires_at"],"title":"InboxResponse"},"LocaleIn":{"properties":{"lang":{"type":"string","title":"Lang"}},"type":"object","required":["lang"],"title":"LocaleIn","description":"Which language the reader is on now.\n\nSent when somebody switches language on a reservation they have not used\nyet. Not validated to a fixed set here: locales.normalise falls back to the\nbase language and then to English, so an unknown tag is a page in English\nrather than a 422 the visitor cannot act on."},"MessageOut":{"properties":{"subject":{"type":"string","title":"Subject"},"from_addr":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Addr"},"bounce_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bounce Address"},"size_bytes":{"type":"integer","title":"Size Bytes"},"has_html":{"type":"boolean","title":"Has Html"},"has_text":{"type":"boolean","title":"Has Text"},"forged_headers":{"additionalProperties":true,"type":"object","title":"Forged Headers"}},"type":"object","required":["subject","from_addr","bounce_address","size_bytes","has_html","has_text","forged_headers"],"title":"MessageOut"},"MessageViewOut":{"properties":{"available":{"type":"boolean","title":"Available"},"unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unavailable Reason"},"size_bytes":{"type":"integer","title":"Size Bytes"},"headers":{"items":{"$ref":"#/components/schemas/HeaderOut"},"type":"array","title":"Headers"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"text_truncated":{"type":"boolean","title":"Text Truncated"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"html_truncated":{"type":"boolean","title":"Html Truncated"},"attachments":{"items":{"$ref":"#/components/schemas/AttachmentOut"},"type":"array","title":"Attachments"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_truncated":{"type":"boolean","title":"Source Truncated"}},"type":"object","required":["available","unavailable_reason","size_bytes","headers","text","text_truncated","html","html_truncated","attachments","source","source_truncated"],"title":"MessageViewOut","description":"The raw message, for reading. Absent once its retention window closes."},"ReportOut":{"properties":{"report_url":{"type":"string","title":"Report Url","default":""},"generated_by":{"type":"string","title":"Generated By","default":"Email Spam Tester (https://email-spam-tester.com)"},"slug":{"type":"string","title":"Slug"},"analysis_status":{"type":"string","title":"Analysis Status"},"ai_status":{"type":"string","title":"Ai Status"},"score_compat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score Compat"},"score_ours":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score Ours"},"subscores":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Subscores"},"scoring_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scoring Version"},"complete":{"type":"boolean","title":"Complete"},"errored_checks":{"items":{"type":"string"},"type":"array","title":"Errored Checks"},"ai_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Verdict"},"ai_confidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Confidence"},"received_at":{"type":"string","format":"date-time","title":"Received At"},"checks_ready_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Checks Ready At"},"message":{"anyOf":[{"$ref":"#/components/schemas/MessageOut"},{"type":"null"}]},"panel":{"items":{"$ref":"#/components/schemas/EngineOut"},"type":"array","title":"Panel"},"checks":{"items":{"$ref":"#/components/schemas/CheckOut"},"type":"array","title":"Checks"},"fixes":{"items":{"$ref":"#/components/schemas/FixOut"},"type":"array","title":"Fixes"},"translating":{"type":"boolean","title":"Translating","default":false}},"type":"object","required":["slug","analysis_status","ai_status","score_compat","score_ours","subscores","scoring_version","complete","errored_checks","ai_verdict","ai_confidence","received_at","checks_ready_at","message","panel","checks","fixes"],"title":"ReportOut"},"StatusOut":{"properties":{"slug":{"type":"string","title":"Slug"},"analysis_status":{"type":"string","title":"Analysis Status"},"ai_status":{"type":"string","title":"Ai Status"},"checks_done":{"type":"integer","title":"Checks Done"},"checks_total":{"type":"integer","title":"Checks Total"}},"type":"object","required":["slug","analysis_status","ai_status","checks_done","checks_total"],"title":"StatusOut"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}