# CrawlCheck-Receipt header, v1

An agent that asked CrawlCheck before acting can prove it to the site it acts on.

## Agent side

1. POST https://crawlcheck.io/api/v1/preflight {"domain": "<site>", "action": "read|cite|connect|transact|administer", "agent": "<your agent name>"}
2. If the decision is one your policy accepts, send the request to the site with:

    CrawlCheck-Receipt: v=1; sha256=<the receipt's sha256>

The receipt itself is not sent; only its digest. Send it only to the domain the receipt names, and only before expires_at.

## Site side

Check a header you receive:

    GET https://crawlcheck.io/api/v1/decisions/<sha256>?host=<your host>

The answer gives accept (true when the receipt names your host, has not expired and its decision is allow or warn), the checks behind it, and the full signed receipt, which verifies offline with https://crawlcheck.io/crawlcheck-verify.mjs. What you do with an accepted receipt is your choice: a higher rate limit, fewer challenges, or only a log line.

Cloudflare Worker example (cache the answer per sha256 for its lifetime in your own code if you check often):

    const RE = /sha256=([0-9a-f]{64})/i;
    export default { async fetch(req, env, ctx) {
      const m = (req.headers.get("crawlcheck-receipt") || "").match(RE);
      if (m) {
        const host = new URL(req.url).hostname;
        const r = await fetch("https://crawlcheck.io/api/v1/decisions/" + m[1] + "?host=" + host).then((x) => x.json()).catch(() => null);
        if (r && r.accept) { const res = await fetch(req); const h = new Response(res.body, res); h.headers.set("x-crawlcheck-receipt", "accepted"); return h; }
      }
      return fetch(req);
    } };

## Privacy

CrawlCheck counts site checks per day (checked, found, accepted, by decision and action). It keeps no domains and no agent names from these checks. Daily counts: https://crawlcheck.io/api/v1/decisions
