# Site report: integration kit for agencies

Status: **available**. Price: **US$0.01 per report (x402, USDC on Base)**.
(Both lines are filled in from the shop's services config when this page is served at https://shop.lordevo.com/agency.)

One paid call takes a website URL and a scope. It returns a client-ready audit: evidence-backed findings, a fix plan in
three phases, a list of what needs a human decision, and an honest coverage section. You get JSON (`site-report/1`),
Markdown and a self-contained HTML file. With the white-label fields the documents carry your agency's name and logo and
no Lordevo name, link or upsell.

Everything below is taken from the code of the service (`src/site-report.js`, `src/site-report-render.js`,
`src/services.js`, `src/shop.js`). Nothing here is a promise of rankings, traffic or security.

- Examples (curl, Node, Python, MCP, A2A): [examples/](https://shop.lordevo.com/agency/examples.md)
- Turning a report into a branded client deliverable: [client-report-howto.md](https://shop.lordevo.com/agency/client-report-howto.md)
- Free sample and pilot: [pilot-offer.md](https://shop.lordevo.com/agency/pilot-offer.md)

## What it checks

All checks are passive: plain HTTP GET/HEAD requests and public DNS lookups, as a browser or crawler would make.
No login, no form submission, no active or intrusive tests. robots.txt is obeyed for every page (our crawler's
User-Agent is `LordevoQuickCheck/1.0`, so it shows up under that name in your client's server logs).

- **SEO** (area `seo`), on every page read: title, meta description, H1, canonical, noindex, viewport, lang, Open Graph,
  JSON-LD (required and recommended properties, blocks that do not parse), image alt text, thin content, compression,
  response time, robots.txt, sitemap, hreflang.
- **Security, passive** (area `security_passive`), on the home page: TLS, HTTP to HTTPS redirect, HSTS, CSP,
  X-Frame-Options / frame-ancestors, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, cookie flags, version
  leaks in headers, CAA, security.txt.
- **Links** (area `links`): up to 40 links on the home page (HEAD, GET fallback, robots.txt checked per link), plus the
  HTTP status of every page read.
- **Email authentication** (area `email_auth`), on the registrable domain: MX, SPF (with a lookup count), DMARC, DKIM at
  30 common selectors, MTA-STS, TLS-RPT, BIMI.
- **AI readiness** (area `ai_readiness`), on the home page: AI crawler rules in robots.txt, llms.txt, organisation
  schema, content present without JavaScript, agent discovery files.
- **Site context** (no findings): CMS, framework, CDN and server signatures, used only to say *where* to apply a fix
  (for example "Cloudflare Transform Rules" or "the nginx server block").

Which pages: the home page plus same-host internal links found on the home page, up to `max_pages` (1-10, default 10).
Each link is checked against robots.txt before any request; disallowed pages are listed, not fetched.

No AI model is called. The fix plan is built from the findings by fixed rules, so the same site state gives the same
report.

## What it does NOT do

- It is not a penetration test, a security certification, or legal or compliance advice. Absence of a finding does not
  prove absence of a problem.
- No login areas, no pages behind JavaScript navigation, no pages that are not linked from the home page.
- No more than 10 pages per report. Bigger sites need several reports or another tool.
- DKIM is looked up at 30 common selectors only; a custom selector is not seen (that finding is marked `inferred`).
- Headers, TLS and response times are measured from our server, once. A CDN or firewall may answer differently
  elsewhere.
- No monitoring or re-scans: each call is one point-in-time run. No human review, no PDF, no email delivery.

## Request budget and limits (from the code)

- Per HTTP request: 12 s timeout, 1.5 MB body cap, up to 5 redirects; every DNS answer and every redirect is checked
  against private, loopback and link-local addresses.
- Per check: page reads 25 s, passive security 45 s, email 30 s, links 45 s, AI readiness 40 s, site context 20 s,
  logo 10 s; 150 s for the whole report.
- Pages are read 2 at a time. One report runs at a time; a second request while one is running gets 429 `busy`
  before any payment request.
- Measured on lordevo.com (which we own) on 30 Oct 2026: 7 pages, 83 HTTP requests, 256 KB downloaded, 15.6 s.
  Rough upper bound for 10 pages, estimated from the code: about 150 HTTP requests plus about 80 DNS queries.
- Request body: 16 KB maximum.

## Observed vs inferred

Every finding has `basis`:
- `observed`: measured directly in a response, a DNS record or a file. The `evidence[]` entries show the source check,
  URL, time, kind (`header`, `html`, `dns`, `http_status`, `tls`, `file`, `robots`, `measurement` ...) and the value
  seen.
- `inferred`: a deduction, and `basis_note` says from what. Today these are: DKIM not found (only 30 selectors
  queried), content rendered by JavaScript (deduced from little text plus many scripts), canonical pointing elsewhere
  (intent unknown) and the SPF lookup count (our own recursive count).

Inferred findings, and findings that need a business decision (for example blocked AI crawlers, noindex, DMARC policy
changes), are also listed in `needs_human_review`. A check that failed or was skipped is listed in `coverage.checks`
with its reason; nothing is filled in for it.

## The request

`POST https://shop.lordevo.com/api/svc/site-report/standard`, JSON body:

```json
{
  "url": "https://client-site.example",
  "scope": {
    "areas": ["seo", "security_passive", "links", "email_auth", "ai_readiness"],
    "max_pages": 10,
    "audience": "agency",
    "brand": { "name": "Your Agency", "logo_url": "https://your-agency.example/logo.png" },
    "format": "all"
  },
  "authorization": {
    "attested": true,
    "statement": "I own this website or have written permission from its owner to assess it.",
    "contact": "you@your-agency.example"
  }
}
```

- `url` (required): a public http(s) website.
- `authorization` (required): you state that you own the site or have the owner's permission. There is no ownership
  check; the statement is stored with the delivery.
- `scope` (optional): `areas` (any of the five, default all), `max_pages` 1-10 (default 10), `audience` `agency`
  (default) or `owner`, `brand` (white-label, needs `audience: agency`), `format` `json`, `markdown`, `html` or `all`
  (default). Unknown fields are refused.

## The flow: 402, pay, 200 or 202 + result_url

1. Send the request without payment. You get **402** with the x402 terms in the `PAYMENT-REQUIRED` header (USDC on Base,
   scheme `exact`). If the input is invalid, the 402 body also has `input_hint` (`bad_url`, `bad_scope`, `bad_areas`,
   `bad_max_pages`, `bad_audience`, `bad_brand`, `bad_format`) with the bad `field` and a working `example_input`: fix it
   before you pay, because a paid request with that input answers 400 and is not charged. No payment request at all:
   403 `authorization_required`, 429 `busy`.
2. Any x402 client signs the payment and sends the same request again with `PAYMENT-SIGNATURE`.
3. The shop waits up to about 50 s for the report:
   - finished: **200** with `status: "done"`, the report in `report`, and `report_html_url` + `report_markdown_url`;
   - still running, and at least one check has already succeeded: **202** with `status: "running"`, `progress`,
     `delivery_id` and `result_url`.
4. Poll `result_url` (free, `GET /deliveries/<id>?key=<key>`) every 15-30 s until `status` is `done` or `error`.
   `GET .../report.html?key=` and `.../report.md?key=` answer 409 `not_ready` while the report is running.
   Result links are private (the key is in the URL) and expire after 30 days (then 410).

The response body: `delivery_id`, `status`, `target`, `started_at`, `finished_at`, `summary`, `report` (the
`site-report/1` JSON; with `format: json` the inline Markdown/HTML are left out, the links still work),
`result_url`, `report_html_url`, `report_markdown_url`, `expires_at`, `disclaimer`.

## When you are charged and when you are not

The x402 payment is settled only when the shop answers below 400.

- Refused before the payment request (403, 429, 503 `not_for_sale_yet`): not charged, you were never asked to pay.
- Invalid input sent with a payment: **400**, `"charged": false`; the payment is not settled.
- Every check failed, or nothing had finished within the wait: **502** `delivery_failed`, `"charged": false`; the
  payment is not settled.
- Payment could not be verified: not settled, not charged (see https://shop.lordevo.com/docs/errors).
- **200**: charged once.
- **202**: charged when accepted (something was already measured). If the report fails after that (for example the
  service restarted), the delivery shows `status: "error"`, the failure is logged and the owner is alerted. There is no
  automatic on-chain refund; If a paid job failed, email hello@lordevo.com with the transaction hash and we'll review it, usually within 2 business days.
- A report where some checks failed is still delivered and charged; the failed checks are listed under coverage and in
  `needs_human_review`, and the area shows `(partial)` or `not_checked`.
- MCP: the same rules; a settlement that fails returns `settlement_failed` with `"charged": false`.

## The site-report/1 contract

Full JSON Schema: OpenAPI `components.schemas.SiteReport1` at https://shop.lordevo.com/openapi.json.

- `schema`: `"site-report/1"`; `generated_at`; `target` {`url`, `final_url`, `host`, `domain`}; `scope` (as sent, with
  `brand.logo_embedded`).
- `summary`: per area `health` (`good`, `needs_work`, `poor`, `not_checked`) with `score` 0-100 and finding counts;
  `top_priorities` (up to 5); `counts` by severity; `executive_summary` (plain sentences generated from the counts).
- `findings[]`: `id` (F01...), `area`, `title`, `severity` (critical, high, medium, low, info), `priority` (P1-P4),
  `effort` {`size` S/M/L, `hours`}, `impact`, `basis`, `basis_note`, `evidence[]`, `pages[]`, `fix` {`summary`,
  `where`, `example`}, `verify` (a command or step to confirm the fix), `sources[]`, `owner_role` (developer, content,
  dns_admin), `check_key`.
- `implementation_plan.phases[]`: `this_week`, `next_30_days`, `later`; each item has `finding_id`, `owner_role`,
  effort and `depends_on` (findings to fix first).
- `needs_human_review[]`: {`topic`, `why`, `finding_id`?, `area`, `url`?}.
- `coverage`: pages crawled / blocked by robots / failed / over the limit, every check with `status` (`ok`, `failed`,
  `blocked`, `skipped`), reason and time, `areas_checked`, `areas_not_checked`, `limitations[]`, `not_a_guarantee`.
- `deliverables`: `markdown` and `html` (depending on `format`); `site_context` (detected stack); `meta`.

Within `site-report/1` fields are only added; a rename or removal means `site-report/2` and a dated changelog entry.

## White-label options

- `scope.audience: "agency"` + `scope.brand.name` (1-80 characters): the Markdown and HTML say "Prepared by
  <your name>", the HTML header shows your name, and there is **no** Lordevo name, link or "Generated by" line.
- `scope.brand.logo_url` (optional): a public https URL of a PNG, JPEG, GIF or WebP up to 150 KB. It is fetched once
  (robots.txt obeyed) and embedded as a data: URI, so the HTML file needs no external request. If it cannot be used,
  the header shows the name only and `needs_human_review` says why.
- Without `brand`: "Prepared for the client" (agency) or "Prepared for the site owner" (owner), and one neutral line
  "Generated by Lordevo site-report (site-report/1)." at the end. Never an upsell.
- What white-label does not hide: the result links are on shop.lordevo.com, and your client's server logs show our
  crawler's User-Agent. Download the files and send them yourself (see client-report-howto.md).

## Where else it is described

- Machine-readable: https://shop.lordevo.com/catalog.json (`services`, slug `site-report`), /services.json,
  /openapi.json, /examples.json (`site_report`), /llms.txt, the A2A agent card (skill `site-report`) and the MCP
  tools `site_report` + `site_report_status` (listed only while the service is on sale).
- Errors and charging for every endpoint: https://shop.lordevo.com/docs/errors
- Questions: hello@lordevo.com
