Site Decay API
Structured website check-ups for your tools. Every finding comes from a live check, with the evidence behind it. Machine-readable: OpenAPI spec · issue codes (JSON).
Get a key
Keys are issued per partner after a quick review by a person. Request one below, or programmatically: POST /api/v1/access-request with {"name", "email", "use_case", "organization", "website", "expected_volume", "is_agent"}. If approved, a one-time link to your key is emailed to you.
Authentication
Send your key on every request: Authorization: Bearer sd_… (or X-API-Key: sd_…). Keys are issued per partner through a one-time link.
curl -H "Authorization: Bearer YOUR_KEY" "https://sitedecay.com/api/v1/check?domain=example.com"
Endpoints
| GET /api/v1/check?domain= | Latest result if a check-up ran in the last 24 hours (cached: true), otherwise runs one now, about 15 seconds. &fresh=1 forces a new one; &wait=0 queues it and returns 202 with a batch_id. |
|---|---|
| POST /api/v1/check/batch | Body {"domains": [...], "fresh": false}, up to 100. Returns 202 with batch_id, status_url, already_fresh, queued, invalid, estimated_minutes. |
| GET /api/v1/batch/{batch_id} | total, done, complete, pending[], failed[] and results[] (each a result as below). Domains are checked one at a time, about 4 a minute: poll every 30–60 seconds. |
A result
| domain | The domain checked |
|---|---|
| report_url | Public report with screenshots, safe to share with the site owner |
| overall_status | critical (any critical issue) · needs_attention (any high or medium) · healthy (low/info only) · not_checkable |
| checked_at | When the check-up ran, ISO 8601 UTC |
| issue_count, counts | Total, and per severity: critical high medium low info |
| summary | The top finding in plain English when it's medium or worse, else null |
| platform | Detected builder or CMS (WordPress, Wix, Squarespace…) |
| mobile | Phone measurements from a 390px-wide render: built_for_phones, has_viewport, viewport, zoom_disabled, layout_width (390 fits, ~980 is a shrunken desktop page), page_width, horizontal_scroll, small_text_share, tap_targets, small_tap_targets, phone_tappable. Null on check-ups from before Oct 2026. |
| cached | Single check only: served from a check-up under 24 hours old |
| issues[] | Most severe first. Each: code, severity, label (which check), finding (plain English), fix (or null), evidence[] (the exact observations: hosts, errors, dates) |
{
"domain": "expired.badssl.com",
"report_url": "https://sitedecay.com/check/expired.badssl.com?ref=api-yourname",
"overall_status": "critical",
"checked_at": "2026-10-08T02:31:46+00:00",
"issue_count": 5,
"counts": {"critical": 1, "high": 0, "medium": 2, "low": 2, "info": 0},
"summary": "Anyone who opens https://expired.badssl.com is blocked by a full-page “Not Secure” warning: its certificate expired on Apr 12, 2015.",
"platform": "Custom or unknown",
"cached": false,
"issues": [
{"code": "SSL_BLOCKED", "severity": "critical", "label": "Security certificate",
"finding": "Anyone who opens https://expired.badssl.com is blocked by a full-page “Not Secure” warning: its certificate expired on Apr 12, 2015.",
"fix": "Your host can install a free certificate (Let's Encrypt) that renews itself.",
"evidence": ["https://expired.badssl.com: certificate has expired; certificate dated to Apr 12, 2015; issued for *.badssl.com, badssl.com"]},
{"code": "NO_EMAIL_SETUP", "severity": "medium", "label": "Email for your domain",
"finding": "This domain isn't set up to receive email.", "fix": "If you use email at this domain, its mail records need fixing.", "evidence": []},
{"code": "NO_TAP_TO_CALL", "severity": "low", "label": "Tap-to-call phone link",
"finding": "No: phone visitors can’t tap to call", "fix": null, "evidence": []}
]
}
Issue codes
Stable identifiers: new codes may be added, existing ones won't change meaning.
| Code | Severity | Meaning |
|---|---|---|
| SITE_DOWN | critical | The homepage doesn't load (checked over https and http). |
| SSL_BLOCKED | critical | Browsers block the site with a full-page certificate warning. |
| HIDDEN_SPAM_LINKS | critical | Hidden links to gambling/spam sites were planted in the page code (a hack). |
| SPAM_CONTENT | critical | The page shows injected gambling or pharmacy spam (a hack). |
| SITE_ERROR_PAGE | critical | The site shows a database or server error instead of its pages. |
| SITE_PARKED_OR_SUSPENDED | critical | The address shows a parked, for-sale or suspended page. |
| SSL_EXPIRING_SOON | high | The certificate expires within about two weeks. |
| INSECURE_FORM | high | A contact/appointment form sends what people type without encryption. |
| NOT_MOBILE_FRIENDLY | high | No phone layout: phones show a shrunken desktop page (confirmed by rendering). |
| OUTDATED_WORDPRESS | high | WordPress is well behind the current release (medium when only slightly behind). |
| BLOCKS_SEARCH_ENGINES | high | The homepage tells search engines not to list it (noindex). |
| DOMAIN_EXPIRING | high | The domain registration expires soon. |
| SSL_CHAIN_INCOMPLETE | medium | The certificate is missing its intermediate: Chrome copes, Firefox and many phones don't. |
| SSL_ONE_ADDRESS_BROKEN | medium | https works with or without www, but not both. |
| INSECURE_FILES | medium | The secure page loads some files over plain http (mixed content). |
| NO_HTTPS_REDIRECT | medium | The site opens over plain http and doesn't send visitors to https. |
| REDIRECTS_ELSEWHERE | medium | The address lands on a different website. |
| NO_EMAIL_SETUP | medium | The domain isn't set up to receive email (no working MX). |
| MAINTAINER_GONE | medium | The web designer credited on the site is no longer in business. |
| WWW_ADDRESS_ISSUE | medium | The www. version of the address is broken or goes somewhere else. |
| MISSING_VIEWPORT | medium | No viewport tag, so phones zoom out to a desktop-width page (comes with NOT_MOBILE_FRIENDLY). |
| HORIZONTAL_SCROLL | medium | Has a phone layout, but parts of the page are wider than the screen (sideways scrolling). |
| SMALL_TEXT | medium | Has a phone layout, but 30%+ of the text is under 12px. |
| SMALL_TAP_TARGETS | medium | Has a phone layout, but many links/buttons are under 24x24px (WCAG 2.2 minimum). |
| SEPARATE_MOBILE_SITE | low | Phones get a separate, older-style mobile site. |
| ZOOM_DISABLED | low | The viewport turns off pinch-to-zoom (user-scalable=no or maximum-scale=1). |
| MISSING_META_DESCRIPTION | low | No description for Google to show under the title. |
| WEAK_PAGE_TITLE | low | The homepage title is generic or missing. |
| STALE_FOOTER_YEAR | low | The footer copyright year is several years old. |
| NO_TAP_TO_CALL | low | No tap-to-call phone link for phone visitors. |
| NO_COMPRESSION | low | Pages are sent uncompressed. |
| SLOW_SERVER | low | The server takes over two seconds to answer. |
| HEAVY_HOMEPAGE | low | The homepage's code is over 400 KB. |
| MANY_FILES | low | The homepage loads a very large number of files. |
| HEADING_ISSUE | low | The page's main heading (H1) is missing or repeated. |
| MISSING_ALT_TEXT | low | Many images have no description (alt text). |
| NO_BUSINESS_SCHEMA | low | No structured business details for Google (schema.org). |
| HSTS_OFF | info | Browsers aren't told to always use https (HSTS). |
| NO_SOCIAL_PREVIEW | info | Links shared on social media show no preview card. |
| NO_FAVICON | info | No browser tab icon. |
| HTTP_NOT_ANSWERING | info | Plain http:// doesn't answer (browsers use https anyway). |
| CHECK_BLOCKED | info | The site turned the check away (401/403/429), so it couldn't be read like a visitor would. |
| CONTENT_NOT_CHECKED | info | A security screen stopped the content check; other checks still ran. |
Limits and errors
Each key has a daily limit of new check-ups (cached reads don't count). Errors are JSON {"error": "…"} with 400 (bad domain or body), 401 (missing or unknown key), 404 (unknown batch), 429 (daily limit).