# Page checks

A page check fetches a public product page, extracts the commercial facts it
advertises, and reports whether an MC4P document is present on it. Use it when
you have a URL and nothing else: no product ID, no certificate in hand.

```bash
curl -X POST https://api.mintall.ai/api/v1/mc4p/page-checks \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://merchant.example/products/pendant" }'
```

Returns 200 with the full report.

```json
{
  "id": "...",
  "url": "https://merchant.example/products/pendant",
  "final_url": "https://merchant.example/products/pendant",
  "fetched_at": "2026-09-16T12:00:00Z",
  "platform_detected": "shopify",
  "verdict": "verified",
  "facts": {
    "name": { "value": "Pendant", "source": "jsonld", "field": "name" },
    "price": { "value": "148.00", "source": "jsonld", "field": "offers.price" }
  },
  "mc4p": {
    "present": true,
    "product_id": "...",
    "issued_to": "merchant.example",
    "status": "verified",
    "checks": { "signatureValid": true }
  },
  "findings": [],
  "warnings": [],
  "errors": []
}
```

## Cached and fresh

`freshness` defaults to `cached`, which serves a recent observation of the
page. Pass `fresh` to refetch it.

```bash
curl -X POST https://api.mintall.ai/api/v1/mc4p/page-checks \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://merchant.example/products/pendant", "freshness": "fresh" }'
```

Fresh checks are rate limited to 10 per minute per caller. Past that the
endpoint returns 429. Cached checks are the default for a reason: use `fresh`
when you need to see a change you just made.

## Verdicts

Four verdicts come from Mintall's answer about the document on the page:

| Verdict               | Meaning                                                            |
| --------------------- | ------------------------------------------------------------------ |
| `verified`            | The page carries a signed certificate and every check passed.      |
| `unverified`          | The page identifies a Mintall product but carries no signed claim. |
| `certificate_expired` | The signature is authentic, past its 7-day window.                 |
| `checks_failed`       | A signed certificate, and a check did not pass.                    |

Six come from the fetch itself, decided without asking the registry:

| Verdict               | Meaning                                               |
| --------------------- | ----------------------------------------------------- |
| `no_certificate`      | No `mintall:issuedFor` node on the page.              |
| `invalid_certificate` | A node is present, its `canonicalSource` is unusable. |
| `canonical_mismatch`  | The `canonicalSource` host is not allowlisted.        |
| `product_deleted`     | The named product is not in the registry.             |
| `password_protected`  | The fetch landed on a storefront password gate.       |
| `page_fetch_failed`   | Blocked, timed out, too large, or non-2xx.            |

**unverified and certificate_expired are not failures:** `unverified` means the page carries no signed claim. `certificate_expired`
  means the signature is authentic and the page is out of sync. Neither is
  evidence of a problem with the merchant.

## Facts

Each fact carries the value, the `field` it came from, and the `source` that
produced it: `jsonld`, `meta`, or `html`. The source tells you how much the
value is worth. A price from JSON-LD is structured data the merchant published;
a price scraped from HTML is a guess.

`platform_detected` is `shopify`, `woocommerce`, or `custom`.